728x90
블록, 체인 만들기에 이어서 코드를 작성한다.
1. P2P 구현
Block클래스와 Chain클래스, 웹소켓을 이용하여 P2P 네트워크를 구현한다.
http와 ws(웹소켓)을 사용해서 api들을 구성하고 블록을 가져올 서버를 express로 만든다.
2. express, ws 설치
npm i express
npm i --save-dev @types/express
npm i ws
npm i --save-dev @types/ws
3. ts설정
(1) tsconfig.json
타입스크립트는 node 파일명.js 로 실행시킬수 없다. 그래서 tsconfig.json에 설정을 추가하여야 한다.
{
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "./build/",
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"strict": true,
"target": "ES6",
"removeComments": true,
"lib": ["ES6"],
"allowJs": true,
"typeRoots": ["./node_modules/@types", "/@types"],
"baseUrl": ".",
"paths": {
// @core/ 경로를 쓰면 src/core/ 경로의 모든 파일
"@core/*": ["src/core/*"],
"*": ["@types/*"]
}
},
"ts-node": {
"files": true, // 전역에 d.ts 파일 사용할수 있게
"require": ["tsconfig-paths/register"]
}
}
"ts-node" 속성 추가
(2) package.json
{
"devDependencies": {
"@babel/preset-env": "^7.19.4",
"@babel/preset-typescript": "^7.18.6",
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.14",
"@types/jest": "^29.2.1",
"@types/merkle": "^0.6.1",
"@types/node": "^18.11.9",
"@types/ws": "^8.5.3",
"babel-core": "^6.26.3",
"nodemon": "^2.0.20",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.0",
"typescript": "^4.8.4"
},
"dependencies": {
"crypto-js": "^4.1.1",
"express": "^4.18.2",
"hex-to-binary": "^1.0.1",
"merkle": "^0.6.0",
"ws": "^8.11.0"
},
"scripts": {
"start": "ts-node index.ts"
}
}
4. index.ts
root폴더에 index.ts 파일을 만들고 서버를 만들어준다.
import express from "express";
import { P2PServer } from "@core/server/p2p";
import { BlockChain } from "@core/index";
const app = express();
const ws = new P2PServer();
// ts-node로 열어야 서버 열린다
app.use(express.json());
app.get("/", (req, res) => {
res.send("bit-chain");
});
// 블록 내용 조회
app.get("/chain", (req, res) => {
res.json(ws.getChain());
});
// 블록 채굴
app.post("/mineBlock", (req, res) => {
const { data } = req.body;
const newBlock = ws.addBlock(data);
if (newBlock.isError) return res.send(newBlock.value);
res.json(newBlock.value);
});
// P2PServer 웹소켓 연결 요청
app.post("/addToPeer", (req, res) => {
const { peer } = req.body;
ws.connectToPeer(peer);
});
// 연결된 socket 조회
app.get("/peer", (req, res) => {
const sockets = ws.getSockets().map((socket: any) => {
return res.json(socket);
});
});
app.listen(8000, () => {
console.log("서버 열림");
ws.listen();
});
5. src/core/index.ts
체인을 만들어줄 index.ts 파일을 src 폴더안에 만들어준다.
import { Chain } from "./blockChain/chain";
export class BlockChain {
public chain: Chain;
constructor() {
this.chain = new Chain();
}
}
6. src/core/blockChain/chain.ts
Chain 클래스안에 체인 검증과 검사하는 함수를 만들어준다.
// 체인 검증 코드
public isValidChain(chain: Block[]): Failable<undefined, string> {
// 최초 블록 검사 하는 코드
const genesis = chain[0];
for (let i = 0; i < chain.length; i++) {
const newBlock = chain[i];
const previousBlock = chain[i - 1];
const isValid = Block.isValidNewBlock(newBlock, previousBlock);
if (isValid.isError) return { isError: true, value: isValid.value };
}
return { isError: false, value: undefined };
}
public replaceChain(receivedChain: Block[]): Failable<undefined, string> {
// 본인 체인과 상대방 체인을 검사하는 함수
const latestReceivedBlock: Block = receivedChain[receivedChain.length - 1];
const latestBlock: Block = this.getLatestBlock();
if (latestReceivedBlock.height === 0)
return { isError: true, value: "받은 블럭이 최초 블럭" };
if (latestReceivedBlock.height <= latestBlock.height)
return { isError: true, value: "본인의 블럭보다 길거나 같은 블럭" };
if (latestReceivedBlock.previousHash === latestBlock.hash)
return { isError: true, value: "블럭이 하나 모자라다." };
// 체인을 갱신해줌
this.blockChain = receivedChain;
return { isError: false, value: undefined };
}
7. src/core/server/p2p.ts
p2p.ts파일에서 P2PServer 클래스가 Chain을 상속받고 블록체인의 P2P네트워크에선
네트워크에 참여하는 모든 PC가 클라이언트인 동시에 서버로서의 역할을 담당한다.
서버란 개념이 따로 없고 오로지 동등한 계층의 노드들이 서로 클라이언트와 서버 역할을 동시에 네트워크에서 하게 된다.
그래서 P2P네트워크를 구축할땐 서버 코드랑 클라이언트 코드를 동시에 작성하여야 한다.
결론 : P2P 네트워크 상에서 모든 노드들이 서버이면서 동시에 클라이언트이다.
import { WebSocket } from "ws";
import { Chain } from "@core/blockChain/chain";
// 상수인 값 열거형
enum MessageType {
latest_block,
all_block,
receivedChain,
}
interface Message {
type: MessageType;
payload: any;
}
export class P2PServer extends Chain {
private sockets: WebSocket[];
constructor() {
super();
this.sockets = [];
}
// listen 서버에 들어왔을때
// 클라이언트가 입장 했을때
listen() {
const server = new WebSocket.Server({ port: 5000 });
server.on("connection", (socket) => {
console.log("클라이언트 입장");
this.connectSocket(socket);
});
}
// connectToPeer : 클라이언트 입장하면
// 서버쪽으로 연결 요청시 실행되는 함수
connectToPeer(newPeer: string) {
const socket = new WebSocket(newPeer);
socket.on("open", () => {
this.connectSocket(socket);
});
}
connectSocket(socket: WebSocket) {
this.sockets.push(socket);
this.messageHandler(socket);
const data: Message = {
type: MessageType.latest_block,
payload: {},
};
this.errorHandler(socket);
const send = P2PServer.send(socket);
send(data);
}
getSockets() {
return this.sockets;
}
messageHandler(socket: WebSocket) {
const callback = (data: string) => {
// Message : 통신할때 이벤트들을 구분 처리 해주기 위해 만든 타입
const result: Message = P2PServer.dataParse<Message>(data);
const send = P2PServer.send(socket);
switch (result.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[] = result.payload;
console.log(receivedChain);
break;
}
default:
break;
}
};
socket.on("message", callback);
}
errorHandler(socket: WebSocket) {
const close = () => {
this.sockets.splice(this.sockets.indexOf(socket), 1);
};
// 소켓이 끊겼을때 자르기
socket.on("close", close);
// 에러 발생시
socket.on("error", close);
}
handleChainResponse(
receivedChain: IBlock[]
): Failable<Message | undefined, string> {
const isValidChain = this.isValidChain(receivedChain);
if (isValidChain.isError)
return { isError: true, value: isValidChain.value };
const isValid = this.replaceChain(receivedChain);
if (isValid.isError) return { isError: true, value: isValid.value };
const message: Message = {
type: MessageType.receivedChain,
payload: receivedChain,
};
this.broadcast(message);
return { isError: false, value: undefined };
}
broadcast(message: Message) {
this.sockets.forEach((socket) => P2PServer.send(socket)(message));
}
static dataParse<T>(_data: string): T {
return JSON.parse(Buffer.from(_data).toString());
}
static send(socket: WebSocket) {
return (data: Message) => {
socket.send(JSON.stringify(data));
};
}
}
728x90
'개발 > BlockChain' 카테고리의 다른 글
[BlockChain] TypeScript로 transaction 만들기 (0) | 2022.11.15 |
---|---|
[BlockChain] TypeScript로 지갑 만들기 (0) | 2022.11.11 |
[BlockChain] TypeScript로 체인 만들기 (0) | 2022.11.03 |
[BlockChain] TypeScript로 블록 만들기 (1) | 2022.11.02 |
[블록체인] 자바스크립트로 블록 만들기 (0) | 2022.10.31 |