본문 바로가기

블록체인_9기/⛓ BlockChain

58강_230908_Blockchain(P2P란?, P2P 네트워크 개발)

728x90

오늘 진행된 강의 내용은 생성된 블록체인을 상호작용하여 분산 거래장부(Distributed Ledgers)를 구현하기 위한 P2P(Peer-to-Peer)네트워크 개발에 대해 학습하고 블록체인에 적용해보고자 한다.

이전 포스팅에 소개했던 블록 생성 방법과, 체인 연결방법의 내용을 기반으로 이번 시간에는 P2P 네트워크 개발을 소개하고자 하니,

네트워크 이전의 블록 생성과정을 보고 싶다면 56강을,

 

56강_230904_Blockchain(SHA256, merkle Tree/Root, Block 생성, TypeScript로 Block 생성)

Hash 함수 SHA256 crypto-js 모듈을 사용하여 SHA256 해시로 암호화하는 방법은 현재 블록체인에서 가장 많이 채택해서 사용하고 있는 암호방식이다. SHA256의 특징은 다음과 같다. 출력속도가 빠르고 단

oo0ojw.tistory.com

 

체인에 대한 코드를 보고싶다면 57강을 확인하도록 하자.

 

57강_230905_Blockchain(chain 생성)

오늘 내용은 전 강의 내용을 이어서 진행한다. 전 강의에서 만든 블록을 이용하여 체인을 생성하고, 조회, 비교, 검사 등을 진행한다. 이전 포스팅의 ' TypeScript로 Block 생성 ' 에서 블록을 생성과

oo0ojw.tistory.com

 

P2P 네트워크 구현을 하기 앞서, P2P가 무엇인지 확인해보도록 하자.

 

 

 


P2P (Peer-to-Peer)

P2P 네트워크(Peer-to-Peer Network)란, 네트워크에 참여하는 각 노드(Node)가 중앙 서버 없이 직접 데이터를 주고 받는 방식을 말한다. 각 노드가 서버 역할을 하면서 동시에 클라이언트를 수행하는 것을 의미한다.

 

출처 : https://r-blockchain.tistory.com/23

 

기존 C/S(Client-Server) 방식은 일잔적인 중앙 집중 시스템의 대표적인 구현 방식이다. CS구조는 시스템이 중앙으로 집중되어 있기 때문에 관리하기가 용이하다. 반면 SPOF 문제나 중앙 기관의 도덕적 해이 문제에 취약하다.

P2P(Peer-to-Peer) 구조는 개인 컴퓨터끼리 직접 연결하고 검색함으로써 참여자가 공급자인 동시에 수요자가 되는 형태라 중앙 집중의 구조적인 문제를 해결할 수 있는 구조이다. 다만 탈중앙화되어 효율적인 관리가 어렵고 분산된 데이터에 대한 동기화 과정이 필요하다. 중앙시스템이나 관리자 없이 각 노드가 동등한 자격과 권한으로 서로 데이터를 주고받으면서 자율적으로 유지되는 네트워크이다.

구분 C/S (Client-Server) 구조 P2P(Peer-to-Peer) 구조
특징 중앙집중, 상이한 역할과 지위 분산 노드, 동등한 역할과 지위
장점 효율적 관리, 데이터 통일성 SPOF 문제 해결, 탈중앙, 투명
단점 SPOF 문제, 관리기관의 도덕적 해이 의사결정, 데이터 동기화 문제

 

 

🌐 P2P 방식 종류

Pure P2P

  • 중앙서버를 사용하지 않는 방식이다.
  • 네트워크에 참여하는 모든 컴퓨터는 모두 Server/Client 역할을 수행하며 검색, 데이터 전송에 있어서 같은 책임을 진다.
  •  장점
    • 전체 네트워크가 죽는 일이 거의 불가능하다.
    • 네트워크 확장성이 매우 높다.
    • 네트워크 증가에 따른 비용이 제로에 가깝다.

 

 

Hybrid P2P

  • 기존의 C/S 모델을 완전히 벗어나지 못한 형태의 P2P 방식이다.
  • 중앙 서버가 존재해 중개 및 검색을 제공하고 데이터 전송은 개인 PC(Peer)간에 주고 받는 형식이다.
  • 냅스터(Napster), 소리바다와 같은 파일공유 프로그램이나, 네이트온 같은 메신저를 통한 파일전송 등이 있다.
  • 장점 : 중앙서버가 존재해 검색이 빠르다.
  • 단점 : 네트워크가 커질수록 중앙서버에 부담이 가며 중앙서버가 죽으면 전체 네트워크가 죽는다.

 

 

Super peer

  • Hybrid P2P 방식과, Pure P2P 방식의 장점을 결합해놓은 방식이다.
  • P2P 네트워크에 중앙서버 역할을 하는 Super peer들을 여러개 둬서 네트워크를 구성하는 방법이다.
  • super peer를 구현하기 쉽지 않으며 구현 방식에 따라서 천차만별의 성능을 보인다.

 

 

 

 

 

 


블록체인의 유형

 

최초의 블록체인인 비트코인은 어떤 중앙시스템이나 관리자도 존재하지 않으며 노드 참여에 대한 제한도 없는 완전한 탈중앙화 기반 시스템이다. 이런 비트코인은 Public 블록체인인데, 이 퍼블릭 유형은 활용 과정에서 다양한 문제에 직면하였고, 퍼블릭 비트코인의 문제점을 극복하고자 Private 블록체인과 Consortium 블록체인이 출현하게 되었다. 이 세 유형의 블록체인을 간단하게 알아보고자 한다.

 

Public Blockchain

  • 중앙 시스템이나 관리자가 존재하지 않으며 노드 참여에 대한 제한이 없는 완전한 탈중앙화 기반 시스템이다.
  • 모든 데이터는 누구에게나 투명하게 공개되며 누구든지 데이터를 생성할 수 있다.
  • 문제점
    • 엔터프라이즈 시장에서는 기밀 정보를 철저하게 보호하는 것이 중요하기 때문에 모든 데이터의 투명한 공개는 적절하지 않다.
      • 익명성이 보장된다고 해도 모든 정보가 투명하게 공개된다면 조합이나 예측을 통해 개인의 프라이버시 문제로 이어질 수 있다.
    • 완전히 분산된 구조는 필연적으로 속도와 확장성 문제를 야기한다.
      • public 블록체인은 참여의 제한이 없다 보니 전 세계 누구와 노드로서의 참여가 가능하다.
      • 새로운 거래내역이 생성되어 전 세계 분상된 노드에 모두 전파되고 분산된 장부가 합의에 도달하기 위해서는 절대적인 시간이 필요하다.
    • public 블록체인은 어떠한 중앙화된 조직이나 기구가 없기 때문에 서비스를 수행하는 과정에서 다양한 이슈나 이해관계 충돌이 발생했을 경우, 이를 적절하게 해결 및 중재가 불가하다.
      • 퍼블릭 블록체인에서 충돌이나 의견 불일치는 하드포크와 같은 극단적인 선택으로 귀결되는 경우가 많다.
        • 하드포크(hardfork)는 기존 블록체인을 베이스로 하여 별도의 블록체인을 만들어 분리시키는 작업을 말한다.

 

Private Blockchain

  • 퍼블릭 블록체인의 문제점을 극복하고 엔터프라이즈 시장에서도 블록체인을 활용하기 위한 현실적인 방안으로서, 블록체인의 장점은 유지하며 서비스의 현실적 특성에 맞게 일정 부분 제약 및 변형된 블록체인이다.
  • 중앙시스템을 기준으로 네트워크 참여 자체를 제한할 수 있으며 참여자 중에서도 데이터 유형 및 참여자 권한에 따라 데이터에 대한 접근도 통제할 수 있다.
  • 일정한 제약이나 통제를 통해 기업 시장에서도 활용하고자 하는 블록체인이다.
  • 문제점
    • 중앙 조직이 존재한다면 기존의 C/S 방식과 차별성이 없어진다.
    • 중앙 조직의 통제 하에 운영되는 노드들끼리 검증된 내용의 신뢰성에도 문제가 발생한다.

 

Consortium Blockchain

  • 동일한 목적이나 이해관계를 가지고 있는 다수의 기업과 단체들이 하나의 컨소시엄을 구성하고 그 안에서 작동되도록 만든 블록체인이다.
  • consortium 블록체인은 중앙기구가 존재하며 참여제한이 있다는 측면에서 Private 블록체인과 유사하지만, 컨소시엄은 하나의 중앙 조직이 존재하는 것이 아니라 공동의 목표를 지닌 다수의 여러 기관이 컨소시엄을 구성하여 공정성과 확장성을 보완한 형태이다.
  • 단일 기관에 의한 독단적 의사 결정 및 운영을 컴소시엄 형태로 견제와 균형을 유지한다.
  • 문제점
    • 컨소시엄 회원 간 이해 충돌 문제가 발생했을 경우 해결 및 중재에 어려움이 있을 수 있다.
    • 관련 당사자들을 컨소시엄에 참여시킬 수 있는 유인책이 부족하다는 문제점이 있다.
    • 회원끼리 담합할 경우 탈중앙화의 의미가 퇴색되기도 한다.

 

 

 


P2P 네트워크 개발

 

#. 폴더의 경로는 다음과 같다.

 

1.  src/core/block/block.ts_새 블록의 난이도를 동적으로 조절해 생성 속도를 일정하게 유지한다.

이전 포스팅에서 블록을 생성할 때에는 생성되는 블록의 난이도 값을 고정값으로 설정했었다. 하지만 실제로 블록의 난이도는 고정 값이 아닌 블록의 생성시간의 일관성을 유지해야하기 때문에 블록에 생성시간에 따라 난이도를 조절해야 한다.

실제로 비트 코인은 하나의 비트 코인을 생성하기 위한 시간을 10분으로 설정하고, 이전 비트코인 생성 시간이 10분 보다 짧으면 난이도를 상승시키고, 10분 보다 길면 난이도를 하락시켰다.

이런 난이도 조절을 위한 동작은 getDifficulty 메소드에서 수행한다. 해당 메소드에서 사용되는 중요한 변수는 다음과 같다.

// 블록의 생성 시간을 정의하는 변수 (10분)
const BLOCK_GENRATION_INTERVAL = 10 * 60;
// 블록의 난이도 조절 주기를 지정하는 변수
const DIFFICULTY_ADJUSTMENT_INTERVAL = 10;

다음은 getDifficulty 메소드이다. 반환되는 난이도의 값을 적용할 현재 생성하려는 블록(_newBlock)과 가장 최근에 조절한 난이도를 적용된 블록(_adjustmentBlock), 현재 생성하는 블록의 이전 블록(_previousBlock)을 매개변수로 받는다.

초기의 블록들은 난이도를 낮게 유지해 네트워크의 확장성을 보장하고 초기 참여자들이 블록 생성을 비교적 쉽게 수행할 수 있도록 if문으로 고정 난이도를 설정한다. ' if (_newBlock.height <= 0 ) throw new Error ("높이가 0이 들어왔어요!") ' 코드는 현재 블록의 높이가 0, 즉 제네시스 블록이라는 것이다. 최초의 블록인 제네시스 블록은 난이도를 설정할 수 없으므로 new Error로 예외처리를 적용한 것이다. ' if (_newBlock.height < 10 ) return 0 ' 블록의 높이 10개 단위로 난이도 조절을 하려고 한다. 10의 배수가 아닐 경우에는 이전 난이도로 설정되어야 하므로, 현재 블록의 높이가 10 이하일 경우에는 난이도 0의 값을 갖고, 21이하일 경우에는 1의 값을 갖는다. 

초기 블록들의 난이도를 고정 값으로 설정 했다면, 그 후의 블록들의 난이도를 설정하기 위해 조건문(if문)을 추가한다. ' _newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0 ' 는 현재 블록의 높이가 10(난이도 조절 주기)으로 나누었을 때 나머지 값이 있다면 이전 난이도의 주기를 벗어나지 않은 블록이므로 이전 블록의 난이도(_previousBlock.difficulty)를 사용하도록 한다.(블록이 주기에 맞아 떨어진다면 블록 생성시간에 따라 난이도가 조절되었을테니 나머지 값이 생긴 블록은 주기에서 벗어난 블록일리 없다.)

하지만, 블록의 높이가 주기와 나누었을 때 나머지 값이 없는, 즉 주기에 다다른 블록이라면, 해당 블록의 생성시간을 확인해 난이도를 조절해야한다.  블록의 난이도를 조절하기 위해서 변수 timeToken과 TimeExprcted를 생성한다.

변수 timeToken은 Number 타입이며, 현재블록의 생성시간(_newBlock.timestamp)에서 가장 최근에 난이도가 조절된 블록의 생성시간(_adjustmentBlock.timestamp)을 뺀 값을 반환받는다.

변수 TimeExpected는 Number 타입이며, 블록의 생성시간의 기준(BLOCK_GENRATION_INTERVAL)과 블록 난이도 주기(DIFFICULTY_ADJUSTMENT_INTERVAL)를 곱한 블록의 생성시간 기준, 즉 목표시간을 담고있는 변수이다. (❗️왜 10이라는 상수를 별도로 곱하는건지 이해하지 못하겠음!!!! 확인 필요!!!!)

if문으로 현재 블록의 생성시간이 목표시간의 절반보다 짧았을 경우, 생성시간이 목표했던 시간보다 빨랐다는 의미이므로 이전 블록의 난이도보다 한 단계 높여 블록 생성시간을 목표시간에 맞출 수 있도록 조절한다. 하지만 현재 블록의 생성 시간이 목표 시간의 두 배 이상이 걸렸을 경우, 난이도가 높아 블록의 생성시간이 길어진 것이므로 이전 블록의 난이도보다 한 단계 낮춰 목표시간에 맞출 수 있도록 조절한다. 만약 목표시간보다 너무 짧지도, 너무 길지도 않았다면 이전 블록의 난이도를 그대로 가져갈 수 있도록 최근 난이도가 조절된 블록의 난이도를 반환한다.

static getDifficulty(_newBlock: Block, _adjustmentBlock: Block, _previousBlock: Block): number {
    // 일반적으로 네트워크에서는 2016개 이전 블록의 생성 시간을 보고 난이도를 조절하지만, 지금은 10개로 진행한다.
    if (_newBlock.height <= 0) throw new Error("높이가 0이 들어왔어요!"); // 최초블록이면 안된다.
    if (_newBlock.height < 10) return 0;
    if (_newBlock.height < 21) return 1;
    // 블록의 높이가 20 이하일 경우에는 체크하지 않는다.
    // 블록의 높이가 10의 배수가 아닐 경우에는 이전 블록 난이도로 설정한다.
    // 목표시간은 1블록 당 10분이다. 10개를 만드려면 100분의 시간이 소요되어야 한다.
    // 나머지가 떨어지지 않으면 이전 블록의 난이도를 반환한다.
    if (_newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0)
      return _previousBlock.difficulty;
    
    const timeToken: number = _newBlock.timestamp - _adjustmentBlock.timestamp;
    const TimeExpected: number =
      BLOCK_GENRATION_INTERVAL * 10 * DIFFICULTY_ADJUSTMENT_INTERVAL;

    // 난이도 증가
    // 생성시간이 빨랐다. 총 걸린시간 < 목표시간 / 2 = 이전 블록 난이도  + 1
    if (timeToken < TimeExpected / 2) return _previousBlock.difficulty + 1;

    // 생성시간이 더 걸렸다. 총 걸린시간  > 목표시간 * 2 = 이전 블록 난이도 - 1
    if (timeToken > TimeExpected * 2) return _previousBlock.difficulty - 1;

    return _previousBlock.difficulty;
  }

 

2. src/core/__test/block.test.ts_블록 난이도 조절 테스트 코드를 생성한다.

해당 테스트코드는 현재 블록이 생성되면 이 블록이 이전 난이도의 블록과 비교해서 생성시간이 얼마나 걸렸는지 확인하고, 목표 주기보다 빨랐으면 난이도롤 상향시키고, 느렸으면 난이도를 하향시킨다.

해당 테스트는 for문을 통해 100회의 블록 생성을 시도한다.

각 블록을 생성할 때마다 Block.generateBlock을 실행하여 블록을 생성한다. Block.generateBlock의 매개변수로 체인의 가장 마지막에 저장된 블록을 이전 블록으로 사용하고, 데이터로 'Block'을 받는다. 난이도 조절을 위한 블록으로 현재 블록의 높이 -10 블록의 값을 받는다. 

  it("이전 10번째 블록 or 최초 블록", () => {
    // 현재 블록을 생성한다 가정하고
    // 현재 블록이 생성된 시간이 이전 10번째 블록으로부터 얼마나 걸렸는지
    // 확인을 하고 블록의 정해진 생성 주기보다 빠르면 난이도를 올리고 아니면 내린다.
    for (let i = 0; i < 100; i++) {
      let block = Block.generateBlock(
        newChain.latestBlock(),
        ["block"],
        newChain.getAdjustmentBlock()
      );
      newChain.addToChain(block);
    }
    console.log("newChain", newChain);
    console.log(newChain.getAdjustmentBlock());
  });

 

3. src/core/server/P2P.ts_블록체인 네트워크를 구축하기 위한 P2P 클래스를 생성한다.

메시지 유형에 따라 블록 정보를 요청하고 전체 체인을 요청할 수 있으며, 새로운 블록이 추가될 때마다 다른 노드로 블록을 브로드캐스트하여 동기화를 유지한다. 

MessageType 열거형 (Enum): 다양한 메시지 유형을 식별하기 위한 열거형입니다. latestBlock, allBlock, addBlock과 같은 메시지 유형이 여기에 정의됩니다.

IMassage 인터페이스: 네트워크를 통해 전송되는 메시지를 정의하는 인터페이스입니다. type은 메시지 유형을, payload는 실제 데이터를 나타냅니다.

P2P 클래스: 블록체인 클래스(Chain)를 확장한 클래스로, 네트워크 통신을 관리합니다. 주요 메소드와 역할은 다음과 같습니다:

constructor(): P2P 클래스의 생성자입니다. sockets 배열은 현재 연결된 소켓들을 저장합니다.

getSockets(): 현재 연결된 소켓 목록을 반환합니다.

connectSocket(socket, type): 소켓을 연결하고 메시지를 처리하는 메소드입니다. socket은 연결된 소켓을 나타내며, type은 메시지 유형입니다.

listen(port): 웹 소켓 서버를 특정 포트에서 열어 대기 상태로 만듭니다. 클라이언트의 연결을 수락하고 connectSocket 메소드로 연결된 소켓을 처리합니다.

addToPeer(peer): 다른 노드로부터 연결을 받기 위해 상대 노드의 IP 주소를 입력받고 해당 노드에 연결을 시도합니다.

WebSocket WebSocketServer: ws 모듈에서 제공하는 WebSocket WebSocketServer 클래스를 사용하여 네트워크 통신을 관리합니다. WebSocketServer 특정 포트에서 클라이언트의 연결을 수락하고, WebSocket 실제 연결된 클라이언트와의 통신을 담당합니다.

import Block from "@core/block/block";
import Chain from "@core/chain/chain";
// npm i @types/ws ws 설치
// ws : 기본적인 연결 관련 작업만 있는 모듈
import { WebSocket, WebSocketServer } from "ws";

// enum : 상태를 지정할 때 사용 (ex. run, state, work)
enum MessageType {
  // 알기 쉽게 사용하기 위해.
  // 0, 1, 2 상태를 지정한다 했을 때
  // latestBlock: 마지막 블록을 요청할 때
  latestBlock = 0, // string 문자로 해도 되는데, 문자로 설정할 경우 오타가 발생할 수 있어 오류를 최소화하기 위해 number로 지정.
  // allBlock: 전체 체인을 요청할 때
  allBlock = 1,
  // addBlock: 블록이 추가되서 알려줄 때
  addBlock = 2,
}

interface IMassage {
  // 메시지의 타입
  type: MessageType;
  // 메시지에 대한 값. 데이터
  payload: any;
}

// P2P 클래스는 Chain의 메소드를 상속받아, Chain의 메소드를 사용할 수 있다.
class P2P extends Chain {
  private sockets: Array<any>; // 연결된 socket들 확인

  constructor() {
    super();
    this.sockets = [];
  }

  getSockets(): Array<any> {
    return this.sockets;
  }

  connectSocket(socket: any, type?: MessageType): void {
    // 소켓을 연결하면
    // 하나의 포트가 동적으로 생기고 그 포트에서 소켓을 들고 있는데
    // socket에는 고유의 포트가 들어있는 상태. 충돌방지를 위해 애플리케이션 or 서비스 연결을 하면
    // 동적으로 포트를 지정해준다. (고유포트)
    this.sockets.push(
      `${socket._socket.remoteAddress} : ${socket._socket.remotePort}`
    );
    // socket.send() 메서드를 호출하면 message 이벤트가 실행된다.
    socket.on("message", (_data: string) => {
      const data = JSON.parse(_data.toString());
      switch (data.type) {
        case MessageType.latestBlock:
          // 0이 들어오면 여기
          const message: IMassage = {
            // type
            type: MessageType.latestBlock, // 모든 블록 타입이  실행되는지 확인
            payload: [this.latestBlock()], // 마지막 블록은 patload에 담아서
          };
          // 완성한 객체를 문자열로 치환해서 보낸다.
          socket.send(JSON.stringify(message));
          break;
        case MessageType.allBlock:
          // 1이 들어오면 여기

          break;
        case MessageType.addBlock:
          // 2가 들어오면 여기
          // 검증 로직은 여기에
          const isValid = this.replaceChain(data.payload);
          // 에러가 트루 == 문제가 있으면 종료
          if (isValid.isError) break;

          const message2: IMassage = {
            type: MessageType.addBlock,
            payload: data.payload,
          };
          this.sockets.forEach((item) => {
            // 현재 접속한 유저들에게 메시지를 전송한다.
            item.send(JSON.stringify(message2));
          });
          break;

        default:
          break;
      }
    });
  }

  listen(port: number): void {
    // 현재 로컬에서 서버 생성
    // 웹 소켓 포트 오픈 대기상태
    const server: WebSocketServer = new WebSocket.Server({ port });
    server.on("connection", (socket: WebSocket) => {
      // 소켓 연결 시도하면
      console.log("new socket connection");
      // 연결한 소켓을 배열에도 추가해주고 message 이벤트도 등록해준다.
      this.connectSocket(socket);
    });
  }
  addToPeer(peer: string): void {
    // 상대방이 내 ip에 접속했을 때
    // 소켓을 생성하고 연결한다.
    const socket: WebSocket = new WebSocket(peer);
    // 상대 소켓의 서버 주소를 받아서 연결을 시도한다.
    socket.on("open", () => {
      // 연결이 성공하면 open 이벤트가 실행된다.
      console.log("연결 성공");
      this.connectSocket(socket, MessageType.addBlock);
    });
  }
}

export default P2P;
// 이후에는 ip 주소 연결해서 data를 받을 것이다.

// 방화벽 헤제???? 로컬에서 배포하기 전에 같은 네트워크상에서 소켓 접속 확인해보기
//  네트워크 보안/고급설정/인바운드규칙/새규칙/특정포트접근 가능(8080)/

 

4. src/core/server/index.ts_Express를 사용하여 블록체인 네트워크 서버를 생성하고 블록체인 네트워크를 조작한다.

서버를 생성해, 지금껏 생성한 블록체인 코드들을 클라이언트단과 연결하여 코드가 동작하는 부분을 확인하고자 한다. 그 전에 먼저 express와 p2p 인스턴스를 연다. app 상수는 Express 애플리케이션을 생성하고, ws 상수 P2P 클래스의 인스턴스를 생성하여 P2P 네트워크 관련 기능을 제공한다.

 

Express 미들웨어도 설정한다. 다른 도메인에서도 요청을 수락할 수 있도록 app.use(cors())로 다른 도메인의 접속을 허용한다. app.use(express.json())app.use(express.urlencoded({ extended: false }))를 설정하여, Express 애플리케이션에서 JSON URL-encoded 데이터를 파싱을 허용한다.

클라이언트가 GET /chains 엔드포인트로 GET 요청을 보내면, 현재 블록 체인을 JSON 형식으로 반환한다. 이는 블록 체인의 상태를 클라이언트에게 제공하는 역할한다.

클라이언트가 POST /block/mine 엔드포인트로 POST 요청을 보내면, 새로운 블록을 채굴하고 블록 체인에 추가한다. 요청의 본문에는 블록에 추가할 데이터가 포함되어 서버측으로 전달되어야 한다. 블록이 성공적으로 채굴되면 클라이언트에게 해당 블록을 JSON 형식으로 반환한다.

클라이언트가 GET /peer/add 엔드포인트로 GET 요청을 보내면, 현재 호스트의 IP 주소를 가져와서 P2P 네트워크에 참여하도록 설정한다. 이를 통해 다른 노드와 연결할 있다.

클라이언트가 GET /peer 엔드포인트로 GET 요청을 보내면, 현재 연결된 P2P 네트워크 피어의 목록을 반환한다. 이는 현재 연결된 다른 블록체인 노드들의 정보를 확인하는 사용된다.

import Block from "@core/block/block";
import P2P from "./p2p";
// npm i express
// npm i -D @types/express
// npm i -D @types/cors cors
import express, { Express, Request, Response } from "express";
import os from "os";
import cors from "cors";

const app: Express = express();
const ws: P2P = new P2P();

app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.get("/chains", (req: Request, res: Response) => {
  res.json(ws.get());
  console.log("############", res.json(ws.get()));
});

app.post("/block/mine", (req: Request, res: Response) => {
  // 블록에 기록할 내용을 받고
  const { data }: { data: Array<string> } = req.body;
  const newBlock: Block | null = Block.generateBlock(
    ws.latestBlock(),
    data,
    ws.getAdjustmentBlock()
  );
  if (newBlock == null) res.send("error");
  ws.addToChain(newBlock);
  res.json(newBlock);
});

// 기존에는 post로 작성했었는데 오타 이슈로 get으로 변경. 본인 v4확인도 귀찮
app.get("/peer/add", (req: Request, res: Response) => {
  const netWorkinterface = os.networkInterfaces();
  let v4: string;
  for (const key in netWorkinterface) {
    const Array = netWorkinterface[key];
    for (const value of Array) {
      if (!value.internal && value.family === "IPv4") v4 = value.address;
      // v4 ip 주소
    }
  }
  ws.addToPeer(`ws://${v4}:7545`);
  res.end();
});

app.get("/peer", (req: Request, res: Response) => {
  const sockets = ws.getSockets();
  res.json(sockets);
});

app.listen(8080, () => {
  console.log("server on~");
  ws.listen(7545);
});

// npx ts-node ./src/core/server/index.ts

// 기존에는 cmd ip config에서 v4주소 가져옴

 

5. src/core/server/index.html_블록체인 네트워크와 상호 작용하는 클라이언트 웹 페이지를 생성한다.

"peer 접속" 버튼을 클릭하면 Axios를 사용하여 서버의 /peer/add 엔드포인트에 GET 요청을 보낸다. 이를 통해 서버에 피어 추가 요청을 보냅니다.

"갱신" 버튼(peer)을 클릭하면 서버의 /peer 엔드포인트에 GET 요청을 보내서 피어 정보를 가져와서 <div id="peerView">에 표시합니다.

"갱신" 버튼(Block)을 클릭하면 서버의 /chains 엔드포인트에 GET 요청을 보내서 블록 정보를 가져와서 <div id="blockView">에 JSON 형식으로 표시합니다.

"생성" 버튼을 클릭하면 입력 폼에서 입력한 내용을 바탕으로 새로운 블록을 서버에 채굴 요청하고, 채굴된 블록 정보를 콘솔에 출력합니다.

  <body>
    <div>
      <button id="peer">peer 접속</button>
    </div>
    <div>
      <label for="">peer</label>
      <button id="peerViewBtn">갱신</button>
    </div>
    <div id="peerView"></div>
    <div>
      <label for="">block</label>
      <button id="blockViewBtn">갱신</button>
    </div>

    <div id="blockView"></div>

    <div>
      <label for="">블록생성</label>
      <input type="text" id="blockData" />
      <!-- 블록 바디 내용 -->
      <button id="blockCreate">생성</button>
    </div>
  </body>
  <script>
    peer.onclick = () => {
      axios.get("http://localhost:8080/peer/add");
    };

    const render = async () => {
      const { data: peer } = await axios.get("http://localhost:8080/peer");
      peerView.innerHTML = peer.join("|");
    };

    peerViewBtn.onclick = render;

    const blockRender = async () => {
    const { data: block } = await axios.get("http://localhost:8080/chains");
      blockView.innerHTML = JSON.stringify(block);
    };

    blockViewBtn.onclick = blockRender;
    const _blockCreate = async () => {
      const _blockData = [blockData.value];
      const { data: block } = await axios.post(
        "http://localhost:8080/block/mine",
        { data: _blockData }
      );
      console.log(block);
    };

    blockCreate.onclick = _blockCreate;
  </script>

 

더보기
  • 블록체인의 유형
    • 장세형, 비트코인의 탄생부터 블록체인의 미래까지 명쾌하게 이해하는 비트코인 블록체인 바이블, 위키북스(2021)
728x90