dukDukz

[블록체인] 두 개의 서버 - 접속 및 정보공유 본문

웹 개발/블록체인

[블록체인] 두 개의 서버 - 접속 및 정보공유

헤일리_HJ 2021. 9. 6. 14:54

두개의 서버. 접속만 되게끔 했었는데

서로의 정보를 공유할 수 있게 해보자

$ curl -X POST -d "{data:"hello"}" http://localhost:3000/addPeers

서버 연결을 시켜놓고
서로 웹소켓으로 연결이 되게끔 처리를 해놓으면
연결 시도를 한다.

 

그러면 server2 가 체크를 한다
server1의 블록과 server2의 블록이 같은가? 다른가?
- 임의로 server1의 블럭이 추가 되어 있는 상황이라서 다르다.
- 검토를 한다 (제네시스 블록 제외하고 검토함)
- 그 다음에 다른 부분을 찾아서 바꿔준다.
- 연결할때 블럭을 2개로 바꿔준다. (server2)

 

연결이 된 상태에서 server1에 블럭을 추가하게 되면 server2에도 보내준다.
마찬가지로 server2에서 블럭 4개로 만들면 server1에서도 하나 추가가 된다.

 

[웹소켓으로 데이터를 양산으로 똑같이 뿌려놓는다 - 이래야 합의가 가능해지니까]

 

목적 :

server1에서 server2 연결 요청 

- 연결완료 되면 server2도 똑같은 블록을 가질 수 있도록...
(server1에서 추가되면 server2에서 추가 되게)

 


코드 형태가 redux 랑 많이 흡사 할 것이다.

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

// reducer를 만들겁니다
function initMessageHandeler(ws){
    실행할 소켓쪽 - 서버쪽 내용을 보면 됨 - ws 를 주는곳은 init 부분
}

function init(ws){
    sockets.push(ws)
    initMessageHandeler(ws)
}

여기 코드의 시작은 어디일까?

이 파일은 모듈로 빼놨다.
server.js 에서 실행이 되도록..
ws.wsInit() 이 최초의 실행이 된다.

 

 

[network.js]

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

 

이 부분을 보면
결과물이 server 라는 변수에 들어간다.
server. .이 붙으면 객체라는 것
그 안에 on이라는게 들어간다.

 

"connection" 이라는것은 최초의 접속이 되었을때 실행되는 메시지
그 연결된 통로를 ws 에 담은 것이다.

 

핸드쉐이크 요청 보내면 클라가 응답을 보낸다 그럼 그제서야 connection 이란 메시지가 옴
서로 연결되어 있던 key 값을 ws라는 변수에 담는것이다.
즉 ws 는 server 와 client 간에 연결된 key 값이다
init(ws) // 소켓 키 값

 

function init(ws){
    sockets.push(ws)    배열에 담음 - 누가 접속해 있는지
    initMessageHandeler(ws)  접속이 되어있는데 뭘할건지... 그걸 관리해주는 함수
}

 

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

function initMessageHandeler(ws) {
    // 실행할 소켓쪽 - 서버쪽 내용을 보면 됨 - ws 를 주는곳은 init 부분
    ws.on("message", data => {		// ws.on("이벤트명",이벤트의 결과를 리턴해주는 결과페이지)
        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;
        }
    })
}

1. 변수

initMessageHandeler
: 소켓 키 값에 따라 함수 안에서 처리 할 수 있도록 할 것이다.
블록들을 합치고 검증하고...

 

QUERY_LAST
QUERY_ALL
RESPONSE_BLOCK : 실질적으로 추가할 지 말지 결정하는 매서드

 

 

2. 함수

ws.on("이벤트명",이벤트의 결과를 리턴해주는 결과페이지)
ws.on("message",data=>{  })

여기서 data는 객체 형태를 담고 있는 결과값이다.
{
    type : ""
    data : ""
}
이렇게 될 것이다.

 

type에 따라서 data 의 내용이 바뀔 수 잇도록

const message = JSON.parse(data)
데이터 통신은 무조건 스트링으로 한다.
근데 우리가 사용할 때는 객체이기 때문에 변환을 해줘야한다.


이 안에 총 3가지 케이스가 있다.

 


function init(ws) {
    sockets.push(ws)
    initMessageHandeler(ws)
    initErrorHandeler(ws)
}

function initErrorHandeler(ws){
    ws.on("close",()=>{})       // 서버 종료
    ws.on("error",()=>{})       
}

function closeConnection(ws){
    console.log(`Connection close ${ws.url}`)
    // 내 배열에서 내가 연결된 부분을 지우도록한다.
    sockets.slice(sockets.indexOf(ws),1)    // 내 키값 기준으로 찾고 하나를 자른다.
}

 

initErrorHandeler 추가하고

다시 initMessageHandeler 이 쪽으로 가서 [network.js]

function initMessageHandeler(ws) {
    ws.on("message", data => {
        const message = JSON.parse(data)
        switch (message.type) {
            case MessageAction.QUERY_LAST:      // 마지막 블럭만 만들어서 보내줌
                write(ws,responseLastMsg())
                break;
           ...
        }
    })
}

 

function write 을 사용할 것이다.

write(ws,{   })    // 얘가 dispatch 라고 가정해보면 두번째 인자값에 객체를 리턴해주는 함수를 넣어주는것이다

case MessageAction.QUERY_LAST:      // 마지막 블럭만 만들어서 보내줌
    write(ws,responseLastMsg())
break;

function responseLastMsg(){
    return {
        type : MessageAction.RESPONSE_BLOCK   // 응답을 block으로 해준다는.. type을 반대로 돌릴것이다.
        data : JSON.stringfy([bc.getLastBlock()]) // 마지막 블럭을 어떻게 가져와야 할까?? block0902 에서 가져옴 . 글자 형태로 보내줌
    }
}

왜 마지막 배열을 담았을까?
내가 연결을 할 때 server1 이 2개 server2 가 1개의 블럭을 갖고있을때
마지막 인덱스값을 비교해보면 블럭의 개수를 알 수 있기 때문이다.
개수가 다르면 매칭을 시켜준다.

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

function init(ws) {
    sockets.push(ws)
    initMessageHandeler(ws)
    initErrorHandeler(ws)
    //ws.send(JSON.stringify({type:MessageAction.QUERY_LAST,data:null}))
    write(ws, queryBlockMsg()) // 위에랑 똑같은 코드
}

QUERY_ALL

function initMessageHandeler(ws) {
    ws.on("message", data => {
        const message = JSON.parse(data)
        switch (message.type) {
            ...
            case MessageAction.QUERY_ALL:       // 전체 블럭을 만들어서 보내줌
                write(ws,responseBlockMsg())
                break;
           ...
        }
    })
}

 

block 에 있는...모든 걸 보내는

function responseBlockMsg(){
    return {
        type : MessageAction.RESPONSE_BLOCK 
        data : JSON.stringify(bc.getBlocks())      // 리턴값이 배열로옴 - 블럭을 담고 있는 모든 거를 리턴
    }
}

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


-> 전체 블럭을 보내는 이유?
맞지 않은 부분부터 처리를 해줘야 하는데 
값이 틀렸다? 그럼 그냥 값이 맞는 부분을 고쳐주는게 아니라 
값 전체를 보내서 완전히 그걸로 쓰도록 한다.
(재설정해라..)


RESPONSE_BLOCK

function initMessageHandeler(ws) {
    ws.on("message", data => {
        const message = JSON.parse(data)
        switch (message.type) {
            ...
            case MessageAction.RESPONSE_BLOCK:      // 이걸로 던져줌
    		handleBlockResponse(message)
		break;
        }
    })
}


전달 받은 내용중에 all or last 겠지만

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data)     // 받은 블록 / message 에서 data라는 속성값만 필요함
    const lastBlockReceived = receivedBlocks[receivedBlocks.length-1] // 받은 블록의 마지막 블럭을 선택한다. 얘는 객체
    const lastBlockHeld =  bc.getLastBlock()        // 가지고 있는 블럭의 마지막
}


일단은 이렇게 한다.

index 는 헤더에 있다..
내가 받은 데이터 블럭의 마지막에서 헤더에서 인덱스가 내가 갖고 있는 헤더의 인덱스보다 높다면 그 블럭은 잘못된 블럭

function handleBlockResponse(message){
    const receivedBlocks = JSON.parse(message.data)     // 받은 블록 / 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`
        )
    }else {
        console.log('블럭이 이미 최신화 되어 있습니다');
    }
}


만약 같다면..? 그 부분도 처리해주면 된다.
내용비교를 하는것...

어떻게 넣어줘야 할까??

if(bc.createHasch(lastBlockHeld) === lastBlockReceived.header.previousHash){
    // 같은 경우에는 마지막에 추가만 해주면 된다. addBlock 함수를 쓰면 된다.
    console.log(`마지막 하나만 비어있는 경우, 하나만 추가합니다.`)
    if(bc.addBlock(lastBlockReceived)){
        // 넣어주고. 다른 사람도 검증 할 수 있도록 보내줘야 한다.
        broadcast(responseLastMsg())
    }
}

내가 받은 블럭이 length = 1 즉 제네시스 블럭밖에 없다면?
내가 가지고온 블럭의 배열이 업뎃이 안되어있다. 내가 최신이라는 얘기

else if (receivedBlocks.length === 1) {
    console.log(`peer 로 부터 블록을 연결해야 합니다.`)
    // 이때는 내가 갖고 있는 블럭 전체를 보내줘서 바꿔 끼울 수 있도록 한다.
    broadcast(queryAllMsg())
}

이 부분까지 추가해주면 된다.

 


Random

[block0902.js]
가서 함수를 하나 만들어준다.

function replaceBlock(newBlocks){  }

newBlocks : 내가 받은 전체 배열 => 내가 받은 전체 블록들
Blocks = newBlocks 근데 간단하지가 않음 - 검증을 해줘야 하기 때문...

1. newBlocks 내용을 검증해야 한다.
2. 검증을 한번만 하지 않는다. 렌덤하게 한번만 할 수 있고, 두번 혹은 세번을 할 수 있게 한다.
3. 검증이 끝나면 Blocks = newBlocks 로 바꿔주고
4. 그 다음에 여기서 broadcast 한다.


2번이 포인트..! -> 조건문에 random을 쓴다는 얘기
이거를 만들려면, 어떻게 해야할까??

가라 형식으로 하나 파일을 만들어보자
[loop.js] 생성

$ npm i random  (제일 바깥 폴더에 설치)
const random = require('random')
console.log(random);
console.log(random.int());     혹은 integer() 도 같음
// 0 과 1 이 랜덤하게 나온다.

 

random.boolean() 으로 하면 true or false 가 나옴

두가지의 경우의 수를 랜덤하게 반환해주는 패키지

$ cd src
$ node loop

 


그래서 이 패키지를 [block0902.js] 에서 가져올 것이다.

const random = require('random')
function replaceBlock(newBlocks){   }

 로 돌아와서


[network.js]
에서 
모듈 exports 에 responseLastMsg 를 추가해라

if (isVaildBlock(newBlocks) && newBlocks.length > Blocks.length && random.boolean()) {
    // 여기가 3번
    console.log(`Blocks 배열을 newBlocks 로 교체합니다`)
    // 여기가 4번
    // block.js 에서 broadcast 사용할 수 있나? - 사용가능!
    // network.js 에서 broadcast를 모듈화 했냐는것? - 했음..
    const nw = require('./network')
    Blocks = newBlocks
    nw.broadcast(nw.responseLastMsg())
}

내가 갖고 있는걸 남한테 보내주고 
최신화로 맞춰주는 것..

 

 

여기까지가 기본 세팅


두 대를 돌려서 실행시켜봐야 한다.

src2 폴더를 만들어준다
터미널을 server1 (src)과 server2 (src2) 로 만들어준다.

그리고 각각의 터미널을 분할해서 
server1 은 cd src
server2 는 cd src2
로 넘어간다

server 1 터미널에서 
$ node server
로 열여준다.
3000번 포트로 열어주었고 소켓은 6005번으로 실행시킴

server 2 터미널에서
3001 과 6006 로 바꿔주고
$ node server


window 터미널 켜서 wsl 에 들어가서

server1 과 2 를 addPeers 로 연결
그럼 server 1 , 2 터미널에 
블럭이 이미 최신화 되어 있습니다 라는 콘솔로그가 뜬다

server 2 부분에는 console.log(ws) 도 찍힘


그럼 하나를 다르게 만들어 보자

src/server.js
mineBlock 부분을 보면

const result = bc.addBlock(data)
이렇게 되어있는데
addBlock 에는 broadcast 하는 부분이 없다.
그래서 여기에도 코드를 작성해야 한다.

 

[src/block0902.js]

function mineBlock(blockData){
    const newBlock = nextBlock(blockData)        //새로운 객체를 가진, Object Block {header, body}
    // 블럭을 만들려면 전역배열에다 넣어줘야함. 검증은 addBlock 이 대신해줄것
    // addBlock 도 매개변수 받는걸로 nextBlock을 쓴다.
    // 그래서 addBlock 은 newBlock을 받아서 하는거로 바꿔준다
    if(addBlock(newBlock)){
        // 모든 peer 에게 블록이 추가 되었으니 마지막거를 확인하라고 broadcast로 날려줘야 한다
        const nw = require('./network')
        nw.broadcast(nw.responseLastMsg())
        return newBlock     // 성공하면 객체 반환
    }else{
        return null
    }
}

 

모듈 exports 에도 mineBlock 추가하고

이 부분을 추가해준다.

[src/server.js]

app.post('/mineBlock',(req,res)=>{
    const data = req.body.data      // 배열형태의 data를 받아오고
    const result = bc.mineBlock(data)   // 그러면 {} 또는 false 값이 반환될 것이다.
    if(result == null){
        //res.send(`mineBlock failed`)
        res.status(400).send(`블럭 추가에 오류가 발생됨`)
    }else{
        res.send(result)
    }
})


이렇게 바꿔준다.

 

addBlock(['hello1'])
addBlock(['hello2'])
addBlock(['hello3'])
이 부분 지워주기
- 인자값을 객체로 바꿨으므로 

 


[block0902.js]
createGenesisBlock() 부분을 보면
사실 하드 코딩으로 만들어야 한다.
실행할때 마다 다르게 나오면 안된다.

function createGenesisBlock() {
    const version = "1.0.0"
    ...
    const time = 1630907567
}

 

이렇게 하드 코딩 해줘야함

그래서 GenensisBlock error 가 난거임!
계속 달라지니까..

 

이렇게 바꿔주고


case 1

server1 과 2 둘다 열여주고

 

1. 연결

$ curl -X POST -H "Content-Type:application/json" -d "{\"peers\":[\"ws:localhost:6006\"]}" http://localhost:3000/addPeers

2. data 추가

$ curl http://localhost:3001/mineBlock -X POST -H "Content-Type:application/json" -d "{\"d
ata\":[\"Hello\"]}"

server2에 추가한거임

그러면 자동으로  server1 도 업뎃이 됨


3. 조회

[직접 추가한  3001 번 조회]

$ curl http://localhost:3001/blocks | python3 -m json.tool

[업뎃 된 3000번 조회]

$ curl http://localhost:3001/blocks | python3 -m json.tool

case 2

 

 

1.  server1 만 실행
server2 는 꺼져있음

그런 상태에서 다시 
2. server1 조회 요청하면

$ curl http://localhost:3000/blocks | python3 -m json.tool

 

제네시스 블럭만 나옴

 

3. 이상태에서 1번 서버에 블럭 2개 추가

$ curl http://localhost:3000/mineBlock -X POST -H "Content-Type:application/json" -d "{\"data\":[\"Hello world\"]}"

 

이렇게 두번 추가하고


4. 1번 서버 다시 조회

$ curl http://localhost:3000/blocks | python3 -m json.tool

 

하면 3개가 나옴


5. 그러고 2번 서버 조회

$ curl http://localhost:3001/blocks | python3 -m json.tool

 

당연히 안나옴

 

6. 이 상태에서 2번 서버 열어주고 

$ curl http://localhost:3000/blocks | python3 -m json.tool

 

이걸 하면 1개가 나옴
- 왜? 연결을 아직 안해줬으니까
addPeers 를 해줘야 함!!!

 

7. server 1과 2를 연결

$ curl -X POST -H "Content-Type:application/json" -d "{\"peers\":[\"ws:localhost:6006\"]}" http://localhost:3000/addPeers

 

로 연결하면

 

Blocks 배열을 newBlocks 로 교체합니다

이런게 터미널에 나오면
한 개 이상의 차이점이 있어서 싹 교체 한다는 거다.
그리고 다시 server 2 조회 해보면 server 1 과 동기화 되어 있는 것 을 확인 할 수 있다.

 

 

참고)

서버가 연결되어 있는 상태에서 둘 중 하나를 끄면

Connection close undefined

이렇게 나옴

 


case 1 :
맨 처음에 둘다 켜놓고
한 곳에 추가 하고 두 곳 다 조회 다 업뎃 되었나

case 2 :
서버1에 블럭 3개이상 생성 하고 
그 다음 서버2 키고 addPeers연결 하고 조회 해보기
그 다음에 서버2에도 추가 해보고 둘 다 조회 되는지 - 업뎃되었는가

case 3 :
server 1, 2, 3 을 만들어놓고 서버 다 켜고
6005
3000
나머지는 하나씩 +1 로 하기

3000에 연결하고 여기에 블럭 추가 했을때 2,3 번 에 추가되는지
끈 상태에서 추가하고 나중에 켰을 때 추가되는지

$ curl -X POST -H "Content-Type:application/json" -d "{\"peers\":[\"ws:localhost:6006\",\"ws:localhost:6007\"]}" http://localhost:3000/addPeers

server2 와 3 번을 1에 연결하기



case 4 :
server 1 - server 2
server 2 - server 3 
에 연결해 놓고
data 추가했을 때 되는지??

 

 

case 별로 해봤을 때, 다 잘되는 것을 확인 할 수 있다.