본문 바로가기
블록체인

P2P 통신으로 노드 간 블록 주고받기

by 혀닙 2022. 6. 14.

목차

  1. 코드로 이해하기
  2. 텍스트로 이해하기

 

 

오늘은 P2P 통신으로 클라이언트에게 블록 보내기를 해볼 것이다.

이를 위해 필요한 선수지식은 웹소켓과 관련된 개념이다.

웹소켓에 대해서 잘 모른다면 아래의 블로그를 참고해보자

https://yellow-w.tistory.com/188

https://yellow-w.tistory.com/189

 

 

그럼 다시 본론으로 들어와서,

먼저 코드를 보면서 흐름을 이해해보도록 하자.

 

 

<참고>
각각의 노드는 클라이언트와 서버측의 코드를 모두 실행한다.
즉, 아래의 코드를 실행시킴으로써 체인에 대한 정보를 공유하고 동기화하는 것이라 보면 이해가 쉬울 것이다.

 

 

1. 코드로 읽는 흐름

import { WebSocket } from 'ws';
import { Chain } from '@core/blockchain/chain';

enum MessageType {
    latest_block = 0,
    all_block = 1,
    receivedChain = 2,
}

interface Message {
    type: MessageType;
    payload: any;
}

//실행은 서버만
export class P2PServer extends Chain {
    public sockets: WebSocket[];
    constructor() {
        super();
        this.sockets = []; //broadcast 하기 위한 목적. 내 자신은 포함되지 않음
    }

    //server측 시작하는 코드
    listen() {
        const server = new WebSocket.Server({ port: 7545 }); // 7545번 포트에서 서버를 여는 코드
        server.on('connection', (socket) => {
            //connection 이벤트 발생 시 콜백함수가 실행됨
            //소켓 : 클라이언트가 서버에 보내는 소켓 정보(핸드쉐이크가 일어났을 때 http 헤더와 같은 정보)

            console.log('Websocket connection'); //핸드쉐이크 발생 시점 확인을 위한 코드
            this.connectSocket(socket);
        });
    }

    //client측 연결 코드
    connectToPeer(newPeer: string) {
        const socket = new WebSocket(newPeer);
        //소켓: 서버가 클라이언트한테 보내는 소켓 정보

        socket.on('open', () => {
            //open 이벤트 발생 시 콜백함수 실행
            this.connectSocket(socket);
        });
    }

    //서버와 클라이언트의 핸드쉐이크가 완료되어 연결되었을 때 호출되는 함수
    connectSocket(socket: WebSocket) {
        this.sockets.push(socket); //연결된 클라이언트의 내용을 푸쉬하는 코드
        this.messageHandler(socket); //코드 가독성을 위해 따로 함수로 빼서 작성

        const data: Message = {
            type: MessageType.latest_block,
            payload: {},
        };

        //	핸드쉐이크 완료되서 연결됬을 때, 클라이언트 > 서버로 보내는 메세지(마지막 블럭 요청)
        const send = this.send(socket);
        send(data); //이렇게 잘라서 쓸 수 있기 때문에 고차함수로 만듦
    }
    messageHandler(socket: WebSocket) {
        const callback = (data: string) => {
            console.log('data: ', data); //버퍼
            const message: Message = P2PServer.dataParse<Message>(data);
            const send = this.send(socket);

            //아래의 스위치문의 각 케이스는 서버딴에서 메세지를 생성해서 클라이언트로 보내는 케이스

            switch (message.type) {
                case MessageType.latest_block: {
                    const message: Message = {
                        type: MessageType.all_block,
                        payload: [this.getLatestBlock()],
                    };
                    send(message);
                    break;
                }
                case MessageType.all_block: {
                    const message: Message = {
                        type: MessageType.receivedChain,
                        payload: this.getChain(),
                    };
                    //블록 검증 코드 이후 블록을 넣을 지 말지 결정하는 코드 작성할 예정
                    send(message);
                    break;
                }
                case MessageType.receivedChain:
                    {
                        const receivedChain: IBlock[] = message.payload;
                        console.log(receivedChain);
                        //체인을 바꿔주는 코드 작성할 예정
                    }
                    break;
            }
            const block: IBlock = message.payload;
        };
        socket.on('message', callback);
    }

    send(_socket: WebSocket) {
        return (_data: Message) => {
            //요청을 던질때는 string으로 던짐
            _socket.send(JSON.stringify(_data));
        };
    }

    static dataParse<T>(_data: string): T {
        //어떤 데이터를 받을 지, 또는 안 받을 지 알 수 없기 때문에 제네릭 또는 null
        //응답을 받을 때는 string> object로 parse함
        return JSON.parse(Buffer.from(_data).toString());
    }
}

 

 

 

2. 텍스트로 읽는 흐름

이해를 위해서 단면만을 잘라서 돌아가는 순서를 정리해보자면

 

listen()메서드가 실행되면서 특정 포트에 webSocket 서버를 연다.

클라이언트가 서버에 요청하고, 핸드쉐이크가 성공적으로 이루어지면

클라이언트측 웹소켓에서는 'open'이벤트 발생,

서버측 웹소켓에서는 'connection' 이벤트가 발생하면서

각각의 콜백함수에서 connectSocket 함수가 호출되어, 함수가 실행된다.

 

connectSocket 함수에서는 sockets 배열에 연결된 클라이언트의 정보를 push 하고,

messageHandler 함수를 호출한다.

messageHandler 함수에서는 메세지를 받으면 callback 함수를 실행시킨다.

다시, connectSocket함수로 돌아가서

클라이언트측에서 서버측으로 가장 마지막 블럭의 정보를 요청하는 메세지를 보낸다.

그러면 메세지를 전달받은 서버측에서는 messageHandler 함수내에 있는 callback 함수를 실행시킨다.

callback 함수에서는 각각의 message.type에 맞는 객체 형태의 message 변수를 생성해서 클라이언트에게 응답한다.

 

 

아래의 내용은 다음 게시물에서 다룰 부분

첫번째 응답(가장 마지막 블럭의 정보)를 받은 클라이언트는

마지막 블럭의 정보와 자신이 가지고 있는 이전블럭을 비교한다.

만약 블럭을 비교했는데 블럭의 정보가 일치하지 않는다면

클라이언트는 다시한번 서버측에 정보를 요청할 것이다.

이때 클라이언트가 서버측에 요청하는 정보는 체인 전체의 정보이다.

 

동일한 방식으로 클라이언트가 send(), 서버측이 전달받은 응답을 하면,

응답을 받은 클라이언트가 전체 블록정보를 대조한 다음 블럭을 업데이트할 지, 아니면 return을 할 지 결정한다.

 

'블록체인' 카테고리의 다른 글

이더리움 생태계  (0) 2022.06.27
transaction 생성  (0) 2022.06.22
P2P 통신을 통해 노드 간 체인 주고받기[수정중]  (0) 2022.06.20
채굴(mining)  (0) 2022.06.20
이론1. 블록체인 개념  (0) 2022.06.08

댓글