BlockChain - WebSocket

정종찬·2022년 5월 2일
httpServer.js
// 웹에 명령어를 입력해서 내 노드를 제어하는 서버
// const epress = require('express') // 전부다 가져온다.
import express from 'express';              // 필요한 것만 가져와서 간결하게 쓸수 있다.
import bodyParser from 'body-parser';
import { getBlocks, createBlock } from './block.js';
import { connectionToPeer, getPeers, sendMessage } from './p2pServer.js';
import path from 'path';

// 초기화 함수
const initHttpServer = (myHttpPoryt) => {
    const app = express();
    const __dirname = path.resolve();
    app.use(bodyParser.json());

    app.get('/', (req, res) => {
        res.sendFile(path.join(__dirname, './index.html'));;
    })

    app.get('/blocks', (req, res) => {
        res.send(getBlocks());
    })

    // app.post('/createblock', (req, res) => {
    //     const data = req.body.data
    //     res.send(createBlock(data));        
    // })

    app.post('/createblock', (req, res) => {
        res.send(createBlock(req.body.data));        
    })
    
    app.post('/peers', (req, res) => {
        res.send(getPeers())
    })

    app.post('/addPeer', (req, res) => {
        console.log('/addrPeer : ', req.body.message);
        res.send(connectionToPeer(req.body.data));
    })

    app.post('/sendMessage', (req, res) => {
        res.send(sendMessage(req.body.data))
    })

    app.listen(myHttpPoryt, () => {
        console.log('listening httpServer Port : ', myHttpPoryt);
    })
}

export { initHttpServer }
p2pServer.js
// peer to peer = p2p, 노드 vs 노드, 개인과 개인, 서로가 필요한 정보를 복사해주며 공유해가며 통신
// 다른노드와 통신을 위한 서버 
import WebSocket from 'ws'
import { WebSocketServer } from 'ws'; // 포트만 넣어주면 서버만들어주는 친구

const MessageType = {
    RESPONSE_MESSAGE : 0,
    SENT_MESSAGE : 1

    // 최신 블록 요청
    // 모든 블록 요청
    // 블록 전달 
}

const sockets = []; // 

const getPeers = () =>{
    return sockets;
}

const initP2PServer = (p2pPort) => {
    const server = new WebSocketServer({port:p2pPort})
    server.on('connection', (ws) => {
        initConnection(ws);        
        //console.log("온건가?")
    })
    console.log('listening P2PServer Port : ', p2pPort);
}

const initConnection = (ws) => {
    sockets.push(ws);
    initMessageHandler(ws);
    //console.log("ddd")
}

const connectionToPeer = (newPeer) => {
    console.log(newPeer)
    const ws = new WebSocket(newPeer)
    ws.on('open', () => { initConnection(ws); console.log('Connect peer : ', newPeer ); })
    ws.on('error', () => { console.log('Fail to Connection peer : ', newPeer); })    
}

const initMessageHandler = (ws) => {
    ws.on('message', (data) => {
        const message = JSON.parse(data); // 메시지를 제이슨으로 변경

        switch(message.type)
        {
            case MessageType.SENT_MESSAGE:      // 메시지 받았을 때
                console.log(message.message);
                break;
        }
    })
}

const write = (ws, message) => {
    console.log('write()', message);
    ws.send(JSON.stringify(message)) // 제이슨을 메시지로 변경 
}

const sendMessage = (message) => {
    sockets.forEach( (socket) => {
        write(socket, message);
    });
}

// JSON.parse 와 JSON.stringify 서로 변경할수 있다.
// const responseMessage = () => {
//     console.log()
// }

export { initP2PServer, connectionToPeer, getPeers, sendMessage }
// 블록체인 관련 함수
// 블록 구조 설계

/*
    index : 블록체인의높이 height
    data : 블록에 포함된 모든데이터 (트랜잭션 포함)
    timestamp : 블록이 생성된 시간
    hash : 블록 내부 데이터로 생성한 sha256 값 (블록의 유일성 보장)
    previousHash : 이전 블록의 해쉬(이전 블록을 참조한다)
*/

import CryptoJS from 'crypto-js' // 모듈이 없다고 나오면 npm install 

class Block {
    constructor(index, data, timestamp, hash, previousHash, difficulty, nonce)
    {
        this.index = index;
        this.data = data;
        this.timestamp = timestamp;
        this.hash = hash;
        this.previousHash = previousHash;
        this.difficulty = difficulty;
        this.nonce = nonce;
    }
}

// function getBlocks() {
//     return blocks;
// }


const getBlocks = () => {
    return blocks;
}

const calculateHash = (index, data, timestamp, previousHash, difficulty, nonce) => {
    // return CryptoJS.SHA256(index + data + timestamp + previousHash).toString();
    return CryptoJS.SHA256((index + data + timestamp + previousHash + difficulty + nonce).toString()).toString();

    // 0 하나로 시작하는 hash값을 만드는 매개변수 (nonce)를 찾는다.
    // 0 두개로 시작하는 hash값을 만드는 매개변수를 찾는다. 난이도 오른다.
    // 16진수 64자리 
    // 16진수 1자리 -> 2진수 4자리로 / 256개의 0과 1로 표현

    // return CryptoJS.SHA256((2).toString()).toString();
}

const createGenesisBlock = () => {
    const genesisBlock = new Block(0, 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks', new Date().getTime() / 1000, 0, 0, 0, 0);

    genesisBlock.hash = calculateHash(genesisBlock.index, genesisBlock.data, genesisBlock.timestamp, genesisBlock.previousHash, genesisBlock.difficulty, genesisBlock.nonce);

    return genesisBlock;
}

const createBlock = (blockdata) => {
    const previousBlock = blocks[blocks.length - 1];
    const nextIndex = previousBlock.index + 1;
    const nextTimestamp = new Date().getTime() / 1000;
    const nextDifficulty = 1;
    const nextNonce = findNonce(nextIndex, blockdata, nextTimestamp, previousBlock.hash, nextDifficulty);
    const nextHash = calculateHash(nextIndex, blockdata, nextTimestamp, previousBlock.hash, nextDifficulty, nextNonce);
    
    const newBlock = new Block(nextIndex, blockdata, nextTimestamp, nextHash, previousBlock.hash, nextDifficulty, nextNonce);

    
    if (isValidNewBlock(newBlock, previousBlock))
    {        
    blocks.push(newBlock);
    return newBlock;
    }
    
    console.log('fail to create newblock');
    return null;
}

// 블록의 무결성 검증

/* 
    블록의 인덱스가 이전 블록인덱스보다 1 크다.
    블록의 previousHash가 이전 블록의 hash 이다.
    블록의 구조가 일치해야 한다.
*/

const isValidBlockStructure = (newBlock) => {
    if (typeof (newBlock.index) === 'number' 
     && typeof (newBlock.data) === 'string' 
     && typeof (newBlock.timestamp) === 'number' 
     && typeof (newBlock.hash) === 'string' 
     && typeof (newBlock.previousHash) === 'string' 
     && typeof (newBlock.difficulty) === 'number' 
     && typeof (newBlock.nonce) === 'number') {
        return true;
    }
    return false;
}

/* 
(typeof (newBlock.index) !== 'number' 
          || typeof (newBlock.data) !== 'string' 
          || typeof (newBlock.timestamp) !== 'number' 
          || typeof (newBlock.hash) !== 'string' 
          || typeof (newBlock.previousHash) !== 'string' ) 
*/


const isValidNewBlock = (newBlock, previousBlock) => {
    if (newBlock.index !== previousBlock.index + 1) // !== 타입까지 비교해준다.
    {
        console.log('invalid index');
        return false;
    }
    else if (newBlock.previousHash !== previousBlock.hash) 
    {
        console.log('invalid previous hash');
        return false;
    }
    else if(isValidBlockStructure(newBlock) == false) {
        console.log('invalid previous structure')
        return false;
    }

    return true;
}

// let testHash = calculateHash(10, 20, 30, 40);
// console.log(testHash);
// console.log(testHash.length);

// 문제 해결을 검사하는 함수
const hashMatchDifficulty = (hash, difficulty) => {
    const binaryHash = hexToBinary(hash);
    const requiredPrefix = '0'.repeat(difficulty);

    return binaryHash.startsWith(requiredPrefix);
}

const hexToBinary = (hex) => {
    const lookupTable = {
        '0' : '0000', '1' : '0001', '2' : '0010', '3' : '0011', 
        '4' : '0100', '5' : '0101', '6' : '0110', '7' : '0111',
        '8' : '1000', '9' : '1001', 'a' : '1010', 'b' : '1011',
        'c' : '1100', 'd' : '1101', 'e' : '1110', 'f' : '1111'
    }
    
    // 03cf
    // 0000001111001111

    let binary = '';
    for(let i =0; i < hex.length; i++)
    {
        if(lookupTable[hex[i]]) {
            binary += lookupTable[hex[i]]
        }
        else {
            console.log('invalid hex : ', hex);
            return null;
        }
    }

    return binary;
} 

const findNonce = (index, data, timestamp, previousHash, difficulty) => {
    let nonce = 0;

    while(true)
    {
        let hash = calculateHash(index, data, timestamp, previousHash, difficulty, nonce);

        if (hashMatchDifficulty(hash, difficulty)) {
            return nonce;
        }
        nonce++;
    }
    
}

const blocks = [createGenesisBlock()];

export { getBlocks, createBlock };
profile
dalssenger

0개의 댓글