BlockChain > 'ws' 활용하여 블록체인 만들기(코드 설명)

YU YU·2021년 9월 7일
0

경일_BlockChain

목록 보기
3/24
post-thumbnail

01.websocket이란?

이번 시간에는 websocket을 활용해서 블록체인을 만들어 보도록 하겠다.
websocket은 http통신과는 다른 형식으로 된 프로토콜 형식이다. HTTP통신과 호환이 되며, 처음 접속시 http통신과 handshake가 이루어진다. handshake는 처음 접속 때 한번만 이루어진다.

02.server.js

02-1.역할

블록체인에서 express 서버의 역할은 단 하나이다. block.js와 network.js가 잘 연결되도록 하는 역할을 담당한다. '서버'로서의 역할이 아니라 하나의 클라이언트(?) 적인 역할을 하는 것이다. '서버'의 모양을 하고 있지만 '서버'가 아니다.

02-2. 각 url의 역할들

아래의 코드를 보면 url이 6개임을 알 수 있다.

/blocks

현재 네트워크상의 블록을 가져오는 역할을 한다.

/version

현재 블록체인의 버전을 가져오는 역할을 한다.

/mineBlock

블록체인을 만드는 작업을 한다.

/peers

자신의 peer에 등록된 소켓들의 도메인을 가져오는 역할을 한다.

/addPeers

다른 사람이 내 웹소켓과 통신하기 위해 처음으로 접속하는 포트이다. 이걸 성공해야지 블록체인을 공유할 수 있다.

app.post('/addPeers',(req,res)=>{
    const peers = req.body.peers
    ws.connectionToPeers(peers)
    res.send('success')
})

body에 있는peers를 connectionToPeers라는 함수의 인잣값으로 넣는 것을 알 수 있다.

/stop

서버를 멈추게 한다.
process.exit(0)은 서버를 멈추게 하는 명령어이다. 이 주소를 입력하면 ctrl+c를 입력하지 않아도 서버를 종료시킬 수 있다. 원격으로도 종료시킬 수 있는 것이다.

02-3. 전체 코드


//server.js
const express = require('express')
const app = express()
const port = process.env.PORT || 3001
const bodyParser = require('body-parser')
const bc = require('./block.js')
const ws = require('./network.js')

app.use(bodyParser.json())

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

app.get("/version",(req,res)=>{
    res.send(bc.getVersion())
})


app.post("/mineBlock",(req,res)=>{
    const data = req.body.data 
    const result = bc.mineBlock(data) // 
    if(result === null ) {
        res.status(400).send(`블럭추가에 오류가 발생되었습니다.`)
    } else {
        res.send(result)
    }
})

app.get('/peers',(req,res)=>{
    res.send( ws.getSockets().map( socket => {
        return `${socket._socket.remoteAddress}:${socket._socket.remotePort}`;
    }) )
})

app.post('/addPeers',(req,res)=>{
    const peers = req.body.peers
    ws.connectionToPeers(peers)
    res.send('success')
})

app.get("/stop",(req,res)=>{
    res.send("Server Stop")
    process.exit(0)
})

ws.wsInit()
app.listen(port,()=>{
    console.log(`server start port ${port}`)
})

여기서 ws.Init()의 역할은 좀 더 뒤로 가서 알아보도록 하겠다.


03.block.js

block.js는 블럭을 만드는 함수들을 모아놓은 파일이다. 함수가 많지만 종류는 8가지로 나눌 수 있다.

03-01. 제네시스 블록을 만드는 함수

function createGenesisBlock(){
    const version ="1.0.0"
    const index = 0
    const time = 1630907567
    const previousHash = '0'.repeat(64)
    const body = ['hello block']
    const tree = merkle('sha256').sync(body)
    const root = tree.root() || '0'.repeat(64)

    const header = new BlockHeader(version,index,previousHash,time,root)
    return new Block(header,body)
}

class BlockHeader { 
    constructor(version ,index ,previousHash, time, merkleRoot){
        this.version = version 
        this.index = index 
        this.previousHash = previousHash
        this.time = time  
        this.merkleRoot = merkleRoot 
    }
}

class Block {
    constructor(header,body){
        this.header = header
        this.body = body
    }
}

let Blocks = [createGenesisBlock()] 

createGenesisBlock()은 최초의 블록을 만드는 역할을 한다. 여기선 header부분과 body부분으로 나눠서 Block이란 클래스로 만드는 역할을 한다. (new가 붙었으니 객체의 형태로 return 될 것이다.)

previousHash, time, index, version을 하드코딩 해주었다는 것을 기억하자!

그리고 처음 생성한 블록을 Blocks란 배열에 넣었다. 이 Blocks란 배열은 앞으로 블록들이 생성되어져서 넣어질 공간이다.

03-02. 전체 블록을 반환하는 함수

간단한 함수로 단 2줄이면 끝난다. 이는 나중에 전체의 블럭이 유효한지 검증하는데 쓰인다.

function getBlocks(){
    return Blocks 
}

03-03. 마지막 블록을 반환하는 함수

이 함수는 나중에 내가 가진 블록체인과 남이 가진 블록체인을 비교해서 하나만 모자랄경우 하나만 추가하는데 쓰이는 함수이다.

function getLastBlock() {
   return Blocks[Blocks.length - 1]
}

03-04. 새로운 블록을 만드는 함수

function nextBlock(data){
    const prevBlock = getLastBlock()
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()

    const merkleTree = merkle("sha256").sync(data) 
    const merkleRoot = merkleTree.root() || '0'.repeat(64)

    const header = new BlockHeader(version,index,previousHash,time,merkleRoot);
    return new Block(header,data)
}

function getVersion(){
    const {version} = JSON.parse(fs.readFileSync("../package.json"))
    return version
}

function getCurrentTime(){
    return Math.ceil(new Date().getTime()/1000) 
}

nextBlock이라는 함수는 인잣값으로 받은 data를 body에 넣어서 블록을 생성한다. 여기엔 하드코딩으로 작성된 코드가 없다. previousHash를 createHash(prevBlock) 즉, createHash에 이전 블록을 넣어서 만드는 것 말고는 createGenesisBlock()와 다른 점이 없다. 그럼 createHash에 대해서 알아보자.

03-05. perviousHash를 만드는 함수

function createHash(block){
    const {
        version,
        index,
        previousHash,
        time,
        merkleRoot
    } = block.header
    const blockString = version+index+previousHash+time+merkleRoot;
    const Hash = CryptoJs.SHA256(blockString).toString()
    return Hash
}

위에서 인잣값으로 이전 블록의 값을 받았음을 알 수 있다. 이는 header에 속해있는 값들을 string값으로 만들어 모두 더한 것을 sha256으로 암호화한 결과라는 것을 알 수 있다. 이것이 이전 블록의 주소값이 되는 것이다.

03-06. 새로운 블록을 추가하는 함수

🔑새로운 블록을 만들어 블록체인에 추가하려면 반드시 '검증'을 거쳐야 한다.

검증할 것
1. 현재의 블록
- 타입들이 올바른가
- 인덱스가 올바른가
- previousHash가 올바른가 (이전 블록값 이용)
- body의 내용이 존재하지 않는가? 👉없으면 안됨
- 암호화가 제대로 되었는가
2. 전체적인 블록
- genesisblock이 같은가
- 다른 블록들 검증(현재의 블록 검증방법으로)

블록체인에 추가하는 함수

function addBlock(newBlock){
    if(isValidNewBlock(newBlock, getLastBlock())) {
        Blocks.push(newBlock);
        return true;
    } 
    return false;
}

현재 넣으려는 블록을 검증하는 함수

이 함수는 블록 다른 노드와 블록 하나만 차이나거나, 아니면 블록 전체를 검증할 때 각각 블록들을 검증할 때 쓰이는 함수이다.

function isValidNewBlock(currentBlock,previousBlock){

    if(!isValidType(currentBlock)){
        console.log(`invaild block structrue ${JSON.stringify(currentBlock)}`)
        return false
    }

    if(previousBlock.header.index + 1 !== currentBlock.header.index) {
        console.log(`invaild index`)
        return false
    }
  
    if(createHash(previousBlock) !== currentBlock.header.previousHash){
        console.log(`invaild previousBlock`)
        return false
    }

    if (currentBlock.body.length === 0) {
        console.log(`invaild body`)
        return false
    }

    if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
        console.log(`invalid merkleRoot`)
        return false
    }

    return true
}

function isValidType(block){
    return (
        typeof(block.header.version) === "string" &&
        typeof(block.header.index) === "number" && 
        typeof(block.header.previousHash) === "string" && 
        typeof(block.header.time) === "number" && 
        typeof(block.header.merkleRoot) === "string" &&
        typeof(block.body) === "object" 
    )
    
}

전체적인 블록 내용을 검증하는 함수

function isValidBlock(Blocks){
    if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
        console.log(`genesis error`)
        return false 
    }


    let tempBlocks = [Blocks[0]] 
    for ( let i = 1; i < Blocks.length; i++) {
        if (isVaildNewBlock(Blocks[i],tempBlocks[i-1])) {
            tempBlocks.push(Blocks[i])
        } else {
            return false 
        }
    }

    return true
}

위의 함수는 먼저 제네시스 블록을 검증한 후에, 그 다음에 하나씩 블록을 검증하는 함수이다. 다른 노드와 블록의 차이가 하나를 초과할 때에 제네시스 블록부터 시작해서 잘못되었는지 각각의 블록을 검증한다.(현재의 블록을 추가하는 코드로)

03-07.전체적인 코드

//block.js
const fs = require('fs')
const merkle = require('merkle')
const CryptoJs = require('crypto-js')
const random = require('random')


class BlockHeader { 
    constructor(version ,index ,previousHash, time, merkleRoot){
        this.version = version 
        this.index = index 
        this.previousHash = previousHash
        this.time = time  
        this.merkleRoot = merkleRoot 
    }
}

class Block {
    constructor(header,body){
        this.header = header
        this.body = body
    }
}

let Blocks = [createGenesisBlock()] 

function getBlocks(){
    return Blocks 
}

function getLastBlock() {
   return Blocks[Blocks.length - 1]
}

function createGenesisBlock(){
    const version ="1.0.0"
    const index = 0
    const time = 1630907567
    const previousHash = '0'.repeat(64)
    const body = ['hello block']

    const tree = merkle('sha256').sync(body)
    const root = tree.root() || '0'.repeat(64)

    const header = new BlockHeader(version,index,previousHash,time,root)
    return new Block(header,body)
}

function nextBlock(data){
    const prevBlock = getLastBlock()
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()
    const merkleTree = merkle("sha256").sync(data) // []
    const merkleRoot = merkleTree.root() || '0'.repeat(64)
    const header = new BlockHeader(version,index,previousHash,time,merkleRoot);
    return new Block(header,data)
}

function createHash(block){
    const {
        version,
        index,
        previousHash,
        time,
        merkleRoot
    } = block.header
    const blockString = version+index+previousHash+time+merkleRoot;
    const Hash = CryptoJs.SHA256(blockString).toString()
    return Hash
}


function addBlock(newBlock){
    if(isVaildNewBlock(newBlock, getLastBlock())) {
        Blocks.push(newBlock);
        return true;
    } 
    return false;
}

function mineBlock(blockData){
    const newBlock = nextBlock(blockData) // Object Block {header, body}
    if(addBlock(newBlock)){
        const nw = require('./network')
        nw.broadcast(nw.responseLastMsg())
        return newBlock
    } else {
        return null
    }
}

function isVaildNewBlock(currentBlock,previousBlock){

    if(!isVaildType(currentBlock)){
        console.log(`invaild block structrue ${JSON.stringify(currentBlock)}`)
        return false
    }

    if(previousBlock.header.index + 1 !== currentBlock.header.index) {
        console.log(`invaild index`)
        return false
    }
  
    if(createHash(previousBlock) !== currentBlock.header.previousHash){
        console.log(`invaild previousBlock`)
        return false
    }

    if (currentBlock.body.length === 0) {
        console.log(`invaild body`)
        return false
    }

    if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
        console.log(`invalid merkleRoot`)
        return false
    }

    return true
}

function isVaildType(block){
    return (
        typeof(block.header.version) === "string" &&
        typeof(block.header.index) === "number" && 
        typeof(block.header.previousHash) === "string" && 
        typeof(block.header.time) === "number" && 
        typeof(block.header.merkleRoot) === "string" &&
        typeof(block.body) === "object" 
    )
    
}

function replaceBlock(newBlocks){

    if (isVaildBlock(newBlocks) && newBlocks.length > Blocks.length && random.boolean()) {
        console.log(`Blocks 배열을 newBlocks 으로 교체합니다.`)
        const nw = require('./network')
        Blocks = newBlocks
        nw.broadcast(nw.responseLastMsg())

    } else {
        console.log(`메시지로부터 받은 블록배열이 맞지 않습니다.`)
    }
}


function getVersion(){
    const {version} = JSON.parse(fs.readFileSync("../package.json"))
    return version
}

function getCurrentTime(){
    return Math.ceil(new Date().getTime()/1000) 
}

function isVaildBlock(Blocks){
    if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
        console.log(`genesis error`)
        return false 
    }


    let tempBlocks = [Blocks[0]] 
    for ( let i = 1; i < Blocks.length; i++) {
        if (isVaildNewBlock(Blocks[i],tempBlocks[i-1])) {
            tempBlocks.push(Blocks[i])
        } else {
            return false 
        }
    }

    return true
}



module.exports = {
    getBlocks,
    getLastBlock,
    addBlock,
    getVersion,
    mineBlock,
    createHash,
    replaceBlock
}


04. network.js(websocket)

websocket은 여기서 주요한 역할을 담당한다. 서버의 역할을 담당하면서 클라이언트의 역할도 된다. 왜냐하면 블록체인은 말 그대로 자기 자신이 하나의 서버이자 클라이언트의 역할을 하기때문이다. 그래서 하나의 노드 서버가 종료되어도 모두의 서버가 같은 정보를 가지고 있을 수 있다.

04-1. 서버

function wsInit(){
    const server = new WebSocket.Server({ port:wsPORT})
    server.on("connection",(ws)=>{
        init(ws)

    })
}

처음에 server.js에 있던 코드이다. 이걸로 시작이 된다.
서버의 역할을 하는 코드이다.
WebSocket.Server는 자신의 port번호를 설정함으로써 웹소켓 서버를 개설한다.
그리고 다른 웹소켓 서버가 접속할 때까지 기다리는 역할을 한다.
server.on("connection",callback)은 다른 소켓이 처음으로 접속했을 때 핸드쉐이크가 일어날 때 작동하는 코드이다. ws에는 고유의 키값이 담긴다.

04-2. 클라이언트

function connectionToPeers(newPeers){
    newPeers.forEach(peer=>{ 
       
        const ws = new WebSocket(peer)
        ws.on("open",()=>{ init(ws) })
        ws.on("error",()=>{  console.log("connection failed") })
    })
}

클라이언트 쪽에서는 웹소켓에 접속하기 위에서 다음과 같이 코드를 작성해준다.
cosnt ws = new WebSocket(접속할 url)
그리고 접속이 이루어질 때 작동하게 할 코드가 있다면 ws.on("open",callback)으로 입력해준다.

클라이언트 접속과 서버 접속의 코드 차이
클라이언트가 접속해서 작동을 할 때는
cosnt ws = new WebSocket(접속할 url)으로 접속하고 ws.on("open",콜백함수)의 콜백함수에 코드를 입력해주면 된다.
반면 서버가 접속을 감지할 때는
const server = new WebSocket.Server({ port:자신의 포트번호})
를 입력한 뒤 자신의 포트번호에 들어온 이에게 행할 함수에 대해선
server.on("connection",콜백함수)로 입력해준다.

04-3. 공통적인 코드

let sockets = []

다음과 같이 소켓에 주소를 넣을 공간을 마련한다.

init(ws)

여기서 ws는 상대방의 도메인 값이거나 상대방의 고유한 키값이다.

function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

가장 먼저 실행되는 wsInit()에 포함되어 가장 먼저 실행되는 코드이다. 클라이언트든지 서버든지 이 코드가 실행된다.
먼저, sockets의 배열에 넣는다.
그 다음에 initMessageHandler가 실행이 된다. 만약 에러가 날 경우는 initErrorHandler가 실행이 된다.
그 후에 write함수가 실행이 되는 구조이다.

initMessageHandler(ws)

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

initMessageHandler는 메세지를 받았을 때 그것을 타입에 따라 나누어주는 각기 다른 함수를 실행시키는 역할을 한다.
만약 메세지의 타입이 'MessageAction.QUERY_LAST'이라면 write(ws,responseLastMsg())를 실행시킨다. 이러한 함수의 형태는 react의 reducer의 형태와 아주 비슷하다. socket.io에서 사용자정의 함수가 하는 역할을 type이 한다고 생각하면 편하다. ws는 가벼운 패키지기에 이런 것 하나하나 설정해주어야 한다. 하나의 기법이기에 어렵게 생각하지 않아도 된다.
그리고

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

이기에 결국 이 함수의 형태는 다음과 같이 된다.

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case 0:
                write(ws,{type:2,data:JSON.stringify([bc.getLastBlock()])}) 
            break;
            case 1:
                write(ws,{type:0,data:null})
            break;
            case 2:
                handleBlockResponse(message)
            break;
        }
    })
}

여기에

function write(ws,message){
  ws.send(JSON.stringify(message)) 
}

을 대입하면 initMessageHandler(ws)는 다음과 같이 나타낼 수 있다.

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case 0://query_last
            	ws.send(JSON.stringify({type:2,data:JSON.stringify([bc.getLastBlock()])})) 
            break;
            case 1://query_all
    	         ws.send(JSON.stringify({type:0,data:null})) 
            break;
            case 2://response_block
	          handleBlockResponse(message)
            break;
        }
    })
}

handleBlockResponse(message)

다른 사람의 블럭이 나의 블럭보다 많을 때
👉받은 블록 중 마지막 블록의 previousHash값 = 내 마지막 블록으로 만들어진 암호화 값
      ->하나만 추가한다.(addBlock사용)
👉받은 블록의 길이가 1일 때(내꺼의 블록이 연결되지 않았음?)
      ->모든 블럭을 보낸다.
👉많이 차이가 날 때
      ->블록 전체를 최신화한다. (replaceBlock사용)

만약 다른사람들의 블럭보다 나의 블럭이 더 많을 때는 반대로 실행이 될 것이다.

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
            if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

broadcast

function broadcast(message){
    sockets.forEach( socket => {
        write(socket,message)
    })
}

내가 연결된 모든 소켓한테 메세지를 보내는 함수이다.

04-04.공통적인 코드 모음


function getSockets(){ return sockets }

const MessageAction = {
    QUERY_LAST:0,
    QUERY_ALL:1,
    RESPONSE_BLOCK:2,
}


function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

function queryBlockMsg(){
    return {
        type:MessageAction.QUERY_LAST,
        data:null
    }
}

function initErrorHandler(ws){
    ws.on("close",()=>{ closeConnection(ws) })
    ws.on("error",()=>{ closeConnection(ws) })
}

function write(ws,message){
  ws.send(JSON.stringify(message)) 
}

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

function responseBlockMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify(bc.getBlocks())
    }
}


function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
        console.log(
            "블록의 갯수 \n" +
            `내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
            `내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
        )
        

        if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

04-5. 전체적인 코드

//network.js
const WebSocket = require('ws')
const wsPORT = process.env.WS_PORT || 6006
const bc = require('./block')

let sockets = []
function getSockets(){ return sockets }

const MessageAction = {
    QUERY_LAST:0,
    QUERY_ALL:1,
    RESPONSE_BLOCK:2,
}

function initMessageHandler(ws){
    ws.on("message",data => {
        const message = JSON.parse(data)
        switch(message.type){
            case MessageAction.QUERY_LAST:
                write(ws,responseLastMsg()) 
            break;
            case MessageAction.QUERY_ALL:
                write(ws,responseBlockMsg())
            break;
            case MessageAction.RESPONSE_BLOCK:
                handleBlockResponse(message)
            break;
        }
    })
}

function queryAllMsg(){
    return {
        type:MessageAction.QUERY_ALL,
        data:null
    }
}


function queryBlockMsg(){
    return {
        type:MessageAction.QUERY_LAST,
        data:null
    }
}

function responseLastMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify([bc.getLastBlock()]) 
    }
}

function responseBlockMsg(){
    return {
        type:MessageAction.RESPONSE_BLOCK,
        data:JSON.stringify(bc.getBlocks())
    }
}

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data) 
    const lastBlockReceived = receivedBlocks[receivedBlocks.length - 1] 
    const lastBlockHeld = bc.getLastBlock() 

    if (lastBlockReceived.header.index > lastBlockHeld.header.index) {
        console.log(
            "블록의 갯수 \n" +
            `내가 받은 블록의 index 값 ${lastBlockReceived.header.index}\n` +
            `내가 가지고있는 블럭의 index 값 ${lastBlockHeld.header.index}\n`
        )
        

        if (bc.createHash(lastBlockHeld) === lastBlockReceived.header.previousHash) {
            console.log(`마지막 하나만 비어있는경우에는 하나만 추가합니다.`)
            if (bc.addBlock(lastBlockReceived)) {
                broadcast(responseLastMsg())
            }
        } else if (receivedBlocks.length === 1) {
            console.log(`피어로부터 블록을 연결해야합니다!`)
            broadcast(queryAllMsg())
        } else {
            console.log(`블럭을 최신화를 진행합니다.`)
            bc.replaceBlock(receivedBlocks)
        }

    } else {
        console.log('블럭이 이미 최신화입니다.')
    }
}

function initErrorHandler(ws){
    ws.on("close",()=>{ closeConnection(ws) })
    ws.on("error",()=>{ closeConnection(ws) })
}

function closeConnection(ws){
    console.log(`Connection close ${ws.url}`)
    sockets.splice(sockets.indexOf(ws),1)
}


function wsInit(){
    const server = new WebSocket.Server({ port:wsPORT})
    server.on("connection",(ws)=>{
        console.log('ws는 과연 무엇일까요?')
        console.log(ws)
        init(ws)

    })
}

function write(ws,message){ ws.send(JSON.stringify(message)) }
function broadcast(message){
    sockets.forEach( socket => {
        write(socket,message)
    })
}

function connectionToPeers(newPeers){
    newPeers.forEach(peer=>{ 
       
        const ws = new WebSocket(peer)
        ws.on("open",()=>{ init(ws) })
        ws.on("error",()=>{  console.log("connection failed") })
    })
}

function init(ws){
    sockets.push(ws)
    initMessageHandler(ws)
    initErrorHandler(ws)
    write(ws,queryBlockMsg())
}

module.exports = {
    wsInit,
    getSockets,
    broadcast,
    responseLastMsg,
    connectionToPeers,
    
}

05.서버 접속 시 흐름

05-01. 제네시스 블록 생성하기

$ curl http://localhost:3001/blocks
를 zsh터미널에 입력한다.

05-02. 상대방을 나의 소켓에 추가하기

curl -X POST -H "Content-Type:application/json" -d "{\"peers\":[\"ws://상대의 웹소켓 주소및 포트\"]}" http://localhost:3001/addPeers

05-03. 블록체인에 새로운 블록 만들기

$ curl -X POST -H "Content-Type:application/json" -d"{\"data\":[\"데이터 내용\"]}" http://localhost:3001/mineBlock
위와 같이 터미널에 입력한다. 그러면 블록이 생성된다.

05-04. 전체적인 블록 확인하기

$ curl http://localhost:3001/blocks
를 zsh터미널에 입력한다.

만약 파이썬이 깔려있다면 다음과 같이 입력하면 잘 정리된 json파일을 볼 수 있다.
$ curl http://localhost:3001/blocks | python3 -m json.tool

profile
코딩 재밌어요!

0개의 댓글