From 768c76202fef275f9bb5bcd1ad39c780c4bf5e13 Mon Sep 17 00:00:00 2001 From: Kiyoshi Mizumoto Date: Thu, 26 Apr 2018 23:50:26 +0900 Subject: [PATCH] =?UTF-8?q?Router=E3=82=AF=E3=83=A9=E3=82=B9=EF=BC=88P2P?= =?UTF-8?q?=E3=83=8D=E3=83=83=E3=83=88=E3=83=AF=E3=83=BC=E3=82=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/__tests__/blockchain/blockchain.test.js | 18 +++++ src/__tests__/blockchain/transaction.test.js | 7 +- src/__tests__/miner/index.test.js | 6 +- src/__tests__/wallet/index.test.js | 6 +- src/blockchain/blockchain.js | 15 ++++ src/config.js | 4 + src/index.js | 12 ++- src/miner/index.js | 7 +- src/router/index.js | 58 +++++++++++++++ src/router/message_types.js | 13 ++++ src/router/p2p_server.js | 78 ++++++++++++++++++++ src/wallet/index.js | 12 ++- yarn.lock | 6 ++ 14 files changed, 226 insertions(+), 19 deletions(-) create mode 100644 src/router/index.js create mode 100644 src/router/message_types.js create mode 100644 src/router/p2p_server.js diff --git a/package.json b/package.json index 4ab1b3f..8542c6f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "crypto-js": "^3.1.9-1", "elliptic": "^6.4.0", "express": "^4.16.3", - "uuid": "^3.2.1" + "uuid": "^3.2.1", + "ws": "^5.1.1" } } diff --git a/src/__tests__/blockchain/blockchain.test.js b/src/__tests__/blockchain/blockchain.test.js index 7cc76f3..48224b4 100644 --- a/src/__tests__/blockchain/blockchain.test.js +++ b/src/__tests__/blockchain/blockchain.test.js @@ -107,4 +107,22 @@ describe('Blockchain', () => { chain.push(justRightBlock); expect(Blockchain.calcDifficultyTarget(chain)).toBe(difficultyTarget); }); + + it('replaceChain test', () => { + blockchain.addBlock(newBlock); + const genesis = Block.genesis(); + + // 短いチェーンは無視 + blockchain.replaceChain([genesis]); + expect(blockchain.chain).toHaveLength(2); + + // 有効でないチェーンは無視 + blockchain.replaceChain([genesis, new Block(0, 'xxx', 256, 0, [])]); + expect(blockchain.chain[1]).toBe(newBlock); + + // 長くて有効なチェーンは置き換える + const newBlock2 = new Block(new Date(), newBlock.hash(), 256, 0, [], MINING_DURATION); + blockchain.replaceChain([genesis, newBlock, newBlock2]); + expect(blockchain.chain).toHaveLength(3); + }); }); diff --git a/src/__tests__/blockchain/transaction.test.js b/src/__tests__/blockchain/transaction.test.js index 19f6422..3487b1b 100644 --- a/src/__tests__/blockchain/transaction.test.js +++ b/src/__tests__/blockchain/transaction.test.js @@ -4,6 +4,8 @@ import SHA256 from 'crypto-js/sha256'; import Blockchain, { Transaction } from '../../blockchain'; import Wallet from '../../wallet'; import Miner from '../../miner'; +import Router from '../../router'; + import { MINING_REWARD } from '../../config'; describe('Transaction', () => { @@ -11,8 +13,9 @@ describe('Transaction', () => { let miner: Miner; beforeEach(() => { const blockchain = new Blockchain(); - wallet = new Wallet(blockchain); - miner = new Miner(blockchain, wallet.publicKey); + const router = new Router(blockchain, false); + wallet = new Wallet(blockchain, router); + miner = new Miner(blockchain, wallet.publicKey, router); }); it('createOutputs test', () => { diff --git a/src/__tests__/miner/index.test.js b/src/__tests__/miner/index.test.js index bac576b..45941be 100644 --- a/src/__tests__/miner/index.test.js +++ b/src/__tests__/miner/index.test.js @@ -3,6 +3,7 @@ import Blockchain, { Transaction } from '../../blockchain'; import Miner from '../../miner'; import Wallet from '../../wallet'; +import Router from '../../router'; describe('Miner', () => { let blockchain; @@ -11,8 +12,9 @@ describe('Miner', () => { beforeEach(() => { blockchain = new Blockchain(); - wallet = new Wallet(blockchain); - miner = new Miner(blockchain, wallet.publicKey); + const router = new Router(blockchain, false); + wallet = new Wallet(blockchain, router); + miner = new Miner(blockchain, wallet.publicKey, router); }); it('mine test', () => { diff --git a/src/__tests__/wallet/index.test.js b/src/__tests__/wallet/index.test.js index b450f3c..42cdb81 100644 --- a/src/__tests__/wallet/index.test.js +++ b/src/__tests__/wallet/index.test.js @@ -4,6 +4,7 @@ import { ec as EC } from 'elliptic'; import Blockchain, { Transaction } from '../../blockchain'; import Miner from '../../miner'; import Wallet from '../../wallet'; +import Router from '../../router'; import { MINING_REWARD } from '../../config'; const ec = new EC('secp256k1'); @@ -15,8 +16,9 @@ describe('Miner', () => { beforeEach(() => { blockchain = new Blockchain(); - wallet = new Wallet(blockchain); - miner = new Miner(blockchain, wallet.publicKey); + const router = new Router(blockchain, false); + wallet = new Wallet(blockchain, router); + miner = new Miner(blockchain, wallet.publicKey, router); }); it('balance test', () => { diff --git a/src/blockchain/blockchain.js b/src/blockchain/blockchain.js index 6cd4110..159b233 100644 --- a/src/blockchain/blockchain.js +++ b/src/blockchain/blockchain.js @@ -65,6 +65,21 @@ class Blockchain { return true; }); } + + replaceChain(newChain: Array) { + if (newChain.length < this.chain.length) { + console.log('チェーンが短いため無視します。'); + return; + } + + if (!Blockchain.isValidChain(newChain)) { + console.log('チェーンが有効でないため無視します。'); + return; + } + + this.chain = newChain; + console.log('ブロックチェーンを更新しました。'); + } } export default Blockchain; diff --git a/src/config.js b/src/config.js index 255d029..267b515 100644 --- a/src/config.js +++ b/src/config.js @@ -2,10 +2,14 @@ const DIFFICULTY_TARGET = 240; const MINING_DURATION = 5000; const MINING_REWARD = 50; const HTTP_PORT = process.env.HTTP_PORT || 3001; +const P2P_PORT = process.env.P2P_PORT || 5001; +const PEERS = process.env.PEERS ? process.env.PEERS.split(',') : []; module.exports = { DIFFICULTY_TARGET, MINING_DURATION, MINING_REWARD, HTTP_PORT, + P2P_PORT, + PEERS, }; diff --git a/src/index.js b/src/index.js index 0b0367c..6effaa3 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,7 @@ import express from 'express'; import bodyParser from 'body-parser'; import Blockchain from './blockchain'; +import Router from './router'; import Wallet from './wallet'; import Miner from './miner'; import { HTTP_PORT } from './config'; @@ -12,9 +13,9 @@ app.use(bodyParser.urlencoded({ extended: true })); app.use(express.static(`${__dirname}/public`)); const bc = new Blockchain(); -const wallet = new Wallet(bc); -const miner = new Miner(bc, wallet.publicKey); - +const router = new Router(bc); +const wallet = new Wallet(bc, router); +const miner = new Miner(bc, wallet.publicKey, router); app.get('/blocks', (req, res) => { const r = bc.chain.map((b, i) => { @@ -32,10 +33,7 @@ app.get('/transactions', (req, res) => { app.post('/transact', (req, res) => { const { recipient, amount } = req.body; - const tx = wallet.createTransaction(recipient, Number(amount)); - if (tx) { - miner.pushTransaction(tx); - } + wallet.createTransaction(recipient, Number(amount)); res.redirect('/transactions'); }); diff --git a/src/miner/index.js b/src/miner/index.js index 83abfe5..520d5b4 100644 --- a/src/miner/index.js +++ b/src/miner/index.js @@ -1,16 +1,20 @@ // @flow import Blockchain, { Block, Transaction } from '../blockchain'; +import Router from '../router'; class Miner { transactionPool: Array; blockchain: Blockchain; rewardAddress: string; + router: Router; - constructor(blockchain: Blockchain, rewardAddress: string) { + constructor(blockchain: Blockchain, rewardAddress: string, router: Router) { this.transactionPool = []; this.blockchain = blockchain; this.rewardAddress = rewardAddress; + this.router = router; + this.router.subscribe(this); } mine() { @@ -39,6 +43,7 @@ class Miner { this.blockchain.addBlock(block); this.clearTransactions(); + this.router.mineDone(); } pushTransaction(tx: Transaction) { diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..51c1a1b --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,58 @@ +// @flow + +import WebSocket from 'ws'; +import Blockchain, { Block, Transaction } from '../blockchain'; +import Miner from '../miner'; +import P2pServer from './p2p_server'; +import MESSAGE_TYPES from './message_types'; +import type { MessageData } from './message_types'; + +class Router { + blockchain: Blockchain; + miner: Miner; + sockets: Array; + p2pServer: P2pServer; + + constructor(blockchain: Blockchain, p2pEnabled: bool = true) { + this.blockchain = blockchain; + this.p2pServer = new P2pServer(this.blockchain, this.messageHandler.bind(this), p2pEnabled); + } + + subscribe(miner: Miner) { + this.miner = miner; + } + + pushTransaction(tx: Transaction) { + if (this.miner) { + this.miner.pushTransaction(tx); + } + this.p2pServer.broadcastTransaction(tx); + } + + mineDone() { + this.p2pServer.broadcastMined(); + } + + messageHandler(data: MessageData) { + switch (data.type) { + case MESSAGE_TYPES.BLOCK_CHAIN: + this.blockchain.replaceChain(data.payload.map(o => Block.fromJSON(o))); + break; + case MESSAGE_TYPES.TRANSACTION: + if (this.miner) { + this.miner.pushTransaction(Transaction.fromJSON(data.payload)); + } + break; + case MESSAGE_TYPES.MINED: + this.blockchain.replaceChain(data.payload.map(o => Block.fromJSON(o))); + if (this.miner) { + this.miner.clearTransactions(); + } + break; + default: + break; + } + } +} + +export default Router; diff --git a/src/router/message_types.js b/src/router/message_types.js new file mode 100644 index 0000000..ccdf729 --- /dev/null +++ b/src/router/message_types.js @@ -0,0 +1,13 @@ +const MESSAGE_TYPES = { + BLOCK_CHAIN: 'BLOCK_CHAIN', + TRANSACTION: 'TRANSACTION', + MINED: 'MINED', +}; + +type MessageData = { + type: string, + payload: any, +}; + +export default MESSAGE_TYPES; +export type { MessageData }; diff --git a/src/router/p2p_server.js b/src/router/p2p_server.js new file mode 100644 index 0000000..fd4a7ca --- /dev/null +++ b/src/router/p2p_server.js @@ -0,0 +1,78 @@ +// @flow + +import WebSocket from 'ws'; +import Blockchain, { Transaction } from '../blockchain'; +import MESSAGE_TYPES from './message_types'; +import type { MessageData } from './message_types'; +import { P2P_PORT, PEERS } from '../config'; + +class P2pServer { + blockchain: Blockchain; + messageHandler: Function; + sockets: Array; + + constructor(blockchain: Blockchain, messageHandler: Function, p2pEnabled: bool) { + this.sockets = []; + this.blockchain = blockchain; + this.messageHandler = messageHandler; + if (p2pEnabled) { + this.listen(); + } + } + + listen() { + try { + const server = new WebSocket.Server({ port: P2P_PORT }); + server.on('connection', socket => this.connect2Socket(socket)); + this.connect2Peers(); + console.log(`P2P Listening on ${P2P_PORT}`); + } catch (e) { + console.log('P2Pサーバの起動に失敗しました。'); + } + } + + connect2Peers() { + PEERS.forEach((peer) => { + const socket = new WebSocket(peer); + socket.on('open', () => this.connect2Socket(socket)); + }); + } + + connect2Socket(socket: WebSocket) { + this.sockets.push(socket); + console.log('Socket connected'); + socket.on('message', (message) => { + console.log(`received from peer: ${message}`); + const data: MessageData = JSON.parse(message); + this.messageHandler(data); + }); + this.sendBlockchain(socket); + } + + sendBlockchain(socket: WebSocket) { + P2pServer.sendData(socket, { + type: MESSAGE_TYPES.BLOCK_CHAIN, + payload: this.blockchain.chain.map(b => b.toJSON()), + }); + } + + broadcastTransaction(transaction: Transaction) { + this.sockets.forEach(socket => P2pServer.sendData(socket, { + type: MESSAGE_TYPES.TRANSACTION, + payload: transaction.toJSON(), + })); + } + + broadcastMined() { + this.sockets.forEach(socket => P2pServer.sendData(socket, { + type: MESSAGE_TYPES.MINED, + payload: this.blockchain.chain.map(b => b.toJSON()), + })); + } + + static sendData(socket: WebSocket, data: MessageData) { + socket.send(JSON.stringify(data)); + } +} + +export default P2pServer; diff --git a/src/wallet/index.js b/src/wallet/index.js index bd4ff26..dc0c61e 100644 --- a/src/wallet/index.js +++ b/src/wallet/index.js @@ -3,6 +3,7 @@ import uuidv1 from 'uuid/v1'; import { ec as EC } from 'elliptic'; import Blockchain, { Transaction } from '../blockchain'; +import Router from '../router'; const ec = new EC('secp256k1'); @@ -10,19 +11,22 @@ class Wallet { blockchain: Blockchain; keyPair: any; publicKey: string; + router: Router; - constructor(blockchain: Blockchain) { + constructor(blockchain: Blockchain, router: Router) { this.blockchain = blockchain; this.keyPair = ec.genKeyPair({ entropy: uuidv1() }); this.publicKey = this.keyPair.getPublic().encode('hex'); + this.router = router; } - createTransaction(recipient: string, amount: number): Transaction { + createTransaction(recipient: string, amount: number) { if (amount > this.balance()) { console.log('残高不足です。'); - return null; + return; } - return Transaction.createTransaction(this, recipient, amount); + const tx = Transaction.createTransaction(this, recipient, amount); + this.router.pushTransaction(tx); } balance() :number { diff --git a/yarn.lock b/yarn.lock index 025e101..a26856f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4808,6 +4808,12 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" +ws@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.1.1.tgz#1d43704689711ac1942fd2f283e38f825c4b8b95" + dependencies: + async-limiter "~1.0.0" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"