위 링크에서 만들었던 Block을 이용하여 Chain을 만들것이다.
1. Block을 이용해서 Chain만들기
chain클래스에는 생성한 블록을 배열로 담아서 블록체인을 만들것이다.
이미 만든 Block이 체이닝을 이루고 있는데 이전 블록 해시값을 속성으로 가지고 있기 때문에 특정 블록 기준으로 이전 블록 해시값이 달라지면 현재 블록의 이전 해시값과 불일치가 발생해서 연결 고리가 끊긴다.
chain을 만드는 이유는 마이닝을 할때 난이도를 계산하기 위해 따로 chain클래스를 따로 만들어서 생성된 블록을 하나의 배열안에 담아주는 역할을 할 예정이다.
블록체인상의 모든 참여자는 동인한 순서로 블록을 연결하기 위해서 합의 알고리즘이 필요하고 대표적인 합의 알고리즘은 POW, POS, DPOS, POA등이 있다.
나는 POW를 이용해서 Chain을 만들것이다.
2. POW(Proof Of Work : 작업 증명)
작업증명 방식은 거래가 발생되면 해당 거래가 유용한지에 대한 합의 검증 방식이다.
구체적으로 POW는 목표값 이하의 해시 값을 찾는 과정을 무수히 반복해서 해당작업에 참여했음을 증명하는 방식의 알고리즘이다.
작업 증명의 기본 개념은 1933년에 고안됐고 1997년 영국의 암호학자인 애덤백의 해시캐시고 이후에 2009년 이 기술을 사토시 나카모토라는 사람이 비트코인에 적용이 되어 오늘날까지 사용된다.
해시캐시는 대량으로 스팸메일을 방지하고자 고안된것이다.
마이닝은 이메일을 보내기 위해서 작업증명 알고리즘을 이용해서 해시값을 찾고 그 보상으로 발행되는 우표와 같다.
작업증명 알고리즘의 필요성은 네트워크상의 모든 노드가 동시에 블록을 만들수 없게 하는것에 있다.
작업증명을 통과해야만 새로운 블록을 추가 생성할 수 있게 된다.
3. nonce
마이닝은 간단히 작업증명은 어려운 수학문제를 풀어 보상을 받는 것이라고 할수있다.
이 어려운 수학문제의 정답이 nonce와 같은 개념이다.
4. difficulty(난이도)
난이도는 특정 조건을 지정해주고 해당값에 nonce를 같이 돌려 해싱한값이 조건에 만족하는지 본다.
난이도 설정 예시 - 블록 한개가 생성되는 예상시간을 10분으로 설정하고 10개의 블록을 한묶음으로 해서 블록 한묶음이 생성되는 예상시간을 6000초라는 값을 할당해주고 이후 10개의 블록이 생성되는데 걸리는 시간이 예상시간/2 보다 작은 경우 난이도를 1올리고 예상시간*2 보다 큰경우 난이도를 1감소 시킨다.
5. src/core/config.ts
// 난이도 조절 블록 범위
export const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10;
// 블록 생성 시간 (분) 10*60 == 600
export const BLOCK_GENERATION_INTERVAL: number = 10;
// 블록 생성 시간(초)
export const BLOCK_GENERATION_TIME_UNIT: number = 60;
난이도 설정을 위한 코드를 추가해준다.
6. src/core/blockChain/chain.ts
import { Block } from "@core/blockChain/block";
import { DIFFICULTY_ADJUSTMENT_INTERVAL } from "@core/config";
export class Chain {
private blockChain: Block[];
// 블록체인에 최초 블럭 넣어두기
constructor() {
this.blockChain = [Block.getGENESIS()];
}
public getChain(): Block[] {
return this.blockChain;
}
public getLength(): number {
return this.blockChain.length;
}
public getLatestBlock(): Block {
return this.blockChain[this.blockChain.length - 1];
}
public addBlock(data: string[]): Failable<Block, string> {
const previousBlock = this.getLatestBlock();
const adjustmentBlock: Block = this.getAdjustmentBlock();
const newBlock = Block.generateBlock(previousBlock, data, adjustmentBlock);
const isValid = Block.isValidNewBlock(newBlock, previousBlock);
if (isValid.isError) return { isError: true, value: "블록 추가 에러" };
this.blockChain.push(newBlock);
return { isError: false, value: newBlock };
}
// 생성 시점으로 블록 높이 -10인 블록 구하기
// 현재 높이값 < DIFFICULTY_ADJUSTMENT_INTERVAL : 최초 블록 반환
// 현재 높이값 > DIFFICULTY_ADJUSTMENT_INTERVAL : -10번째 블록 반환
public getAdjustmentBlock() {
const currentLength = this.getLength();
const adjustmentBlock: Block =
this.getLength() < DIFFICULTY_ADJUSTMENT_INTERVAL
? Block.getGENESIS()
: this.blockChain[currentLength - DIFFICULTY_ADJUSTMENT_INTERVAL];
return adjustmentBlock; // 최초블록 or -10번째 블록 반환
}
}
7. @types/hex-to-binary/index.d.ts
난이도 조건과 비교하기 위해 해싱한 16진수를 2진수로 바꾸기 위해 hex-to-binary를 설치한다.
- 루트 폴더에서 라이브러리 설치
npm i hex-to-binary
타입을 설정하기 위해 모듈을 선언해준다.
보통 많이 사용하는 라이브러리는 npm i @types/* 로 설치할수 있지만 해당 라이브러리는 이 기능을 지원안해준다.
그래서 직접 선언을 해줘야 한다.
index.d.ts 파일안에 코드 작성
declare module "hex-to-binary";
8. src/core/blockChain/block.ts
import { SHA256 } from "crypto-js";
import merkle from "merkle";
import { BlockHeader } from "./blockHeader";
import {
DIFFICULTY_ADJUSTMENT_INTERVAL,
BLOCK_GENERATION_INTERVAL,
BLOCK_GENERATION_TIME_UNIT,
GENESIS,
} from "@core/config";
import hexToBinary from "hex-to-binary";
// 부모 속성 가져오고 인터페이스 형태 클래스 만듬
export class Block extends BlockHeader implements IBlock {
public hash: string;
public merkleRoot: string;
public nonce: number;
public difficulty: number;
public data: string[];
constructor(_previousBlock: Block, _data: string[], _adjustmentBlock: Block) {
// 부모 클래스 속성 가져와야 하니까 super사용
super(_previousBlock);
this.merkleRoot = Block.getMerkleRoot(_data);
this.hash = Block.createBlockHash(this);
this.nonce = 0;
this.difficulty = Block.getDifficulty(
this,
_adjustmentBlock,
_previousBlock
);
this.data = _data;
}
// 최초 블록 가져오는 함수
public static getGENESIS(): Block {
return GENESIS;
}
//블록추가
public static generateBlock(
_previousBlock: Block,
_data: string[],
_adjustmentBlock: Block
): Block {
const generateBlock = new Block(_previousBlock, _data, _adjustmentBlock);
const newBlock = Block.findBlock(generateBlock);
return newBlock;
}
// 난이도 구현 함수
public static getDifficulty(
_newBlock: Block,
_adjustmentBlock: Block,
_previousBlock: Block
): number {
if (_newBlock.height <= 9) return 0;
if (_newBlock.height <= 10) return 1;
// 10번째 배수의 블록에 한해서만 난이도 구현
// 10개의 묶음씩 같은 난이도를 가지게 한다.
if (_newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) {
return _previousBlock.difficulty;
}
// 블록 1개당 생성시간 : 10분, 10개 생성되는데 걸리는 시간 6000초
const timmeTaken: number = _newBlock.timeStamp - _adjustmentBlock.timeStamp;
const TimeExpected: number =
BLOCK_GENERATION_INTERVAL *
BLOCK_GENERATION_TIME_UNIT *
DIFFICULTY_ADJUSTMENT_INTERVAL;
if (timmeTaken < TimeExpected / 2) return _adjustmentBlock.difficulty + 1;
if (timmeTaken > TimeExpected * 2) return _adjustmentBlock.difficulty - 1;
return _adjustmentBlock.difficulty;
}
// fincBlock()
// 마이닝 작업코드
public static findBlock(generateBlock: Block) {
let hash: string;
let nonce: number = 0;
while (true) {
nonce++;
generateBlock.nonce = nonce;
hash = Block.createBlockHash(generateBlock);
// hextoBinary(hash) : 16진수 -> 2진수로 변환
// hexToBinary 모듈 설치해서 사용
// 설치명령어
// npm install hex-to-binary
// 타입 설치
// npm i @types/hex-to-binary
const binary: string = hexToBinary(hash);
const result: boolean = binary.startsWith(
"0".repeat(generateBlock.difficulty)
);
if (result) {
generateBlock.hash = hash;
return generateBlock;
}
}
}
// 머클루트 반환 함수
public static getMerkleRoot<T>(_data: T[]): string {
const merkleTree = merkle("sha256").sync(_data);
return merkleTree.root();
}
// 블록 해시 생성 함수
public static createBlockHash(_block: Block): string {
// difficulty, nonce
const {
version,
timeStamp,
height,
merkleRoot,
previousHash,
difficulty,
nonce,
} = _block;
const values: string = `${version}${timeStamp}${height}${merkleRoot}${previousHash}${difficulty}${nonce}`;
return SHA256(values).toString();
}
// 블록 유효 검사 함수(새로운 블록이 생성되면 검증)
public static isValidNewBlock(
_newBlock: Block,
_previousBlock: Block
): Failable<Block, string> {
//블록의 높이가 이전 블록보다 1이 증가된 상태인지 체크하는 식
if (_previousBlock.height + 1 !== _newBlock.height) {
return { isError: true, value: "블록 높이 오류" };
}
// 블록의 이전 블록 해시 값이 새로운 블록의 이전 블록 해시값과 같은지
if (_previousBlock.hash !== _newBlock.previousHash) {
return { isError: true, value: "이전 해시 오류" };
}
// 생성된 블록의 정보를 가지고 다시 해싱해서 생성된 블록의 해시값과 같은지 비교
if (Block.createBlockHash(_newBlock) !== _newBlock.hash) {
return { isError: true, value: "블록 해시 오류" };
}
return { isError: false, value: _newBlock };
}
}
블록에 난이도, nonce를 추가하여 코드를 다시 짜준다.
9. src/core/blockChain/chain.test.ts
만든 체인을 테스트 해보기 위해 test파일을 만들어 준다.
import { Chain } from "@core/blockChain/chain";
describe("체인 검증", () => {
let node: Chain = new Chain();
it("체인 가져오기 확인", () => {
console.log(node.getChain());
});
it("체인 길이 확인", () => {
console.log(node.getLength());
});
it("체인 마지막 블록 확인", () => {
console.log(node.getLatestBlock());
});
it("체인 블록 추가 확인", () => {
for (let i = 0; i < 20; i++) {
node.addBlock([`블록 ${i}번째`]);
console.log(node.getChain());
}
});
});
테스트 실행 명령어
npx jest src/core/blockChain/chain.test.ts
'개발 > BlockChain' 카테고리의 다른 글
[BlockChain] TypeScript로 지갑 만들기 (0) | 2022.11.11 |
---|---|
[BlockChain] TypeScript로 P2P 구현 (0) | 2022.11.07 |
[BlockChain] TypeScript로 블록 만들기 (1) | 2022.11.02 |
[블록체인] 자바스크립트로 블록 만들기 (0) | 2022.10.31 |
[블록체인] 비트코인 (0) | 2022.10.31 |