이번 포스팅에서는 이전 포스팅의 내용을 토대로 제네시스 블록을 만들어 보고 작성된 코드들을 검증해 볼 수 있는 테스트 코드를 작성해 보고자 한다.
이전 글)
2022.06.11 - [BlockChain] - BlockChain - TypeScript로 블록체인 만들기 (1)
< 목차 >
- 제네시스 블록 만들기
- 테스트 코드 작성하기 (feat. Jest)
1. 제네시스 블록 만들기
제네시스 블록은 블록 체인 상에서 첫번째 블록일 일컫는다. 저번 포스팅에서 BlockHeader 클래스와 Block 클래스를 이용해서 블록을 생성하게끔 코드를 작성하였지만 해당 코드만으로는 첫번째 블록을 생성할 수가 없다. 두 클래스 모두 constructor( ) 함수의 인자값으로 이전 블록을 받고 있기 때문이다. 따라서 첫번째 블록을 직접 만들어주도록 하자.
config.ts 파일 안에서 다음과 같이 GENESIS 블록을 만들어 주었다.
// config.ts 파일
export const GENESIS: IBlock = {
version: "1.0.0",
height: 0,
timestamp: new Date().getTime(),
hash: "0".repeat(64),
previousHash: "0".repeat(64),
merkleRoot: "0".repeat(64),
difficulty: 0,
nonce: 0,
data: [
"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks",
],
};
보통의 경우 첫번째 블록은 위와 같이 하드 코딩으로 직접 만들어주는 경우가 많다.
이제 block.ts 파일에서 GENESIS 블록을 불러온 다음 Block 클래스 안에 추가해주도록 하자.
// block.ts 파일
import { SHA256 } from "crypto-js";
import merkle from "merkle";
import { BlockHeader } from "./blockHeader";
import { GENESIS } from "@core/config"; // 추가
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[]) {
// 부모 속성 가져오기
super(_previousBlock);
const merkleRoot = Block.getMerkleRoot(_data);
this.merkleRoot = merkleRoot;
this.hash = Block.createBlockHash(this);
this.nonce = 0;
this.difficulty = 0;
this.data = _data;
}
// 추가
public static getGENESIS(): Block {
return GENESIS;
}
public static getMerkleRoot<T>(_data: T[]): string {
const merkleTree = merkle("sha256").sync(_data);
return merkleTree.root();
}
public static createBlockHash(_block: Block): string {
const { version, timestamp, height, merkleRoot, previousHash } = _block;
const values: string = `${version}${timestamp}${height}${merkleRoot}${previousHash}`;
return SHA256(values).toString();
}
// 추가
public static generateBlock(_previousBlock: Block, _data: string[]): Block {
const generateBlock = new Block(_previousBlock, _data);
return generateBlock;
}
public static isValidNewBlock(
_newBlock: Block,
_previousBlock: Block
): Failable<Block, string> {
if (_previousBlock.height + 1 !== _newBlock.height)
return { isError: true, error: "height error" };
if (_previousBlock.hash !== _newBlock.previousHash)
return { isError: true, error: "previousHash error" };
if (Block.createBlockHash(_newBlock) !== _newBlock.hash)
return { isError: true, error: "block hash error" };
return { isError: false, value: _newBlock };
}
}
Block 클래스 안에서 getGENESIS( ) 라는 static 메소드를 만들어서 GENESIS 블록을 return 하게끔 하였다. 이러한 방식으로 GENESIS 블록을 가져오는 이유는 추후 블록들을 생성하여 연결할 때 ( 체인을 만들 때 ) Block 클래스의 메소드를 이용해 첫번째 블록을 생성하는 것이 편하기 때문이다.
그리고 static 메소드로 generateBlock( ) 이라는 함수를 추가하였는데 해당 메소드를 통해 블록을 생성할 수 있게끔 하기 위해 만들어준 메소드이다.
2. 테스트 코드 작성하기 (feat. Jest)
이제 테스트 코드를 작성해서 지금까지 만든 클래스의 메소드들이 제대로 동작하는지 살펴보도록 하자.
TypeScript를 사용해서 블록 체인을 만든다는 것은 객체지향적인 방법으로 코드를 작성한다는 것이다. 일명 OOP (객체 지향 프로그래밍)라고 할 수 있는데, OOP는 프로그램 설계 방법론이자 개념의 일종이다. OOP에 대해 간략하게 알아보자.
OOP (object-oriented programming, 객체 지향 프로그래밍) 는 프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 "객체 (object)"라는 기본 단위로 나누고 이들의 상호작용으로 서술하는 방식이다. OOP에서는 객체를 하나의 역할을 수행하는 "메소드와 변수(데이터)"의 묶음으로 봐야 한다.
이러한 객체 지향 프로그래밍에서는 프로그램을 만들 때 최소 단위 즉, 작은 기능들부터 만들어가는 방식을 취한다. 그 결과 작성된 코드들에 대한 테스트가 어려워진다는 단점이 존재하고 결국 테스트 코드를 작성하는 일종의 프레임워크를 설치해서 테스트를 진행하는 과정을 거치게 된다. 다시 말해, 테스트 코드를 작성하면서 개발을 진행하게 되는 셈인데 이러한 기법을 TDD (Test-Driven Development) 라고 한다.
TDD에 사용되는 여러 프레임워크들 중 우리가 사용해 볼 것은 바로 Jest 이다. 어떠한 방식으로 테스트 코드를 작성하는지 직접 코드를 살펴보면서 알아가보도록 하자.
우선 Jest를 사용하기 위해 다음의 모듈을 설치해준다.
npm install -D ts-jest @types/jest babel-core
npm install -D @babel/preset-typescript @babel/preset-env
모듈 설치가 완료되었다면 프로젝트의 루트 디렉토리에서 babel.config.js 파일과 jest.config.ts 파일을 생성해준 뒤 다음과 같이 설정 내용을 작성해주도록 하자.
👉 babel.config.js
// babel.config.js 파일
module.exports = {
presets: [
[
"babel/preset-env",
{
targets: { node: "current" },
},
],
"@babel/preset-typescript",
],
};
👉 jest.config.ts
// jest.config.ts 파일
import type { Config } from "@jest/types";
const config: Config.InitialOptions = {
moduleFileExtensions: ["ts", "js"],
testMatch: ["<rootDir>/**/*.test.(js|ts)"], // 테스트 코드를 실행할 파일명
moduleNameMapper: {
// 경로에 별칭 사용시 작성
"^@core/(.*)$": "<rootDir>/src/core/$1",
},
testEnvironment: "node",
verbose: true, // 테스트 실행시 터미널 창에서 테스트 항목 확인
preset: "ts-jest",
};
export default config;
babel.config.js 의 내용은 바벨 설정에 관한 내용이고 jest.config.ts 의 내용 역시 Jest 프레임워크를 어떻게 사용할 것인지에 대한 내용이다. 두 파일 모두 환경설정과 관련된 내용들이므로 자세한 설명은 생략하도록 하겠다.
testMatch의 속성값으로 테스트를 실행할 파일명 형식을 작성해주게 되는데, 위에 작성된 내용은 [파일명].test.js 혹은 [파일명].test.js 로 이름지어진 파일들에 한해서만 테스트를 실행하겠다는 내용이다.
block.ts 파일 안에 작성된 코드들을 테스트 해보기 위해 위와 같이 block.test.ts 파일을 생성하였다. 그리고 block.test.ts 파일 안에서 아래와 같이 코드를 작성해주었다.
// block.test.ts 파일
import { Block } from "@core/blockchain/block";
import { GENESIS } from "@core/config";
describe("Block 검증", () => {
let newBlock: Block;
// it() : 테스트할 최소 단위의 코드를 작성하는 공간
it("블록 생성 테스트", () => {
const data = ["Block #2"];
newBlock = Block.generateBlock(GENESIS, data);
console.log(newBlock);
});
});
Jest를 이용해 테스트 코드를 작성할 때 describe( ) 함수를 사용해서 테스트를 진행하게 된다. 테스트 파일 안에 테스트 함수를 많이 작성해야 할 경우, 연관된 테스트 함수들끼리 그룹핑을 해 놓으면 코드 가독성이 좋아진다. 이 때 describe( ) 함수를 사용하면 되는데, 해당 함수를 통해 여러개의 테스트 함수를 묶는 것이 가능하다.
describe( )의 콜백함수 안에서 사용된 it( ) 함수를 통해 그룹핑 된 테스트 안에서 개별 테스트를 진행하는 것이 가능하다. 즉, it( ) 함수의 콜백함수 안에 테스트 코드를 작성하여 최소 단위의 기능들을 하나하나 테스트 해볼 수 있는 것이다.
it( ) 함수의 콜백함수 안에서 Block.generateBlock( ) 메소드의 인자값으로 GENESIS 블록과 data 변수를 인자값으로 전달하였다. 그리고 console.log( )를 통해 새로 생성된 블록을 출력하도록 테스트 코드를 작성하였다.
위와 같이 테스트 코드 작성을 완료했다면 터미널에 아래의 명령어를 입력해 테스트를 진행해 볼 수 있다.
npx jest
테스트 실행 결과 Block 클래스에서 만들어주었던 generateBlock( ) 메소드가 정상적으로 작동하는 것을 확인할 수 있다.
Block 클래스 안에 있는 isValidNewBlock( ) 메소드 역시 블록 생성시 중요하게 작용하는 함수이므로 테스트 코드를 작성하여 테스트를 진행해보도록 하겠다.
// block.test.ts 파일
import { Block } from "@core/blockchain/block";
import { GENESIS } from "@core/config";
describe("Block 검증", () => {
let newBlock: Block;
// it() : 테스트할 최소 단위의 코드를 작성하는 공간
it("블록 생성 테스트", () => {
const data = ["Block #2"];
newBlock = Block.generateBlock(GENESIS, data);
console.log(newBlock);
});
it("블록 검증 테스트", () => {
const isValidBlock = Block.isValidNewBlock(newBlock, GENESIS);
if (isValidBlock.isError) {
console.error(isValidBlock.error);
return expect(true).toBe(false);
}
expect(isValidBlock.isError).toBe(false);
});
});
"블록 검증 테스트" 안에서 expect( ).toBe( ) 함수가 사용된 것을 볼 수 있는데 해당 함수는 테스트 코드 작성시 자주 사용되는 함수이다. 테스트 코드를 작성해서 테스트를 진행할 때 우리가 원하는 결과값이 나오는 경우에만 테스트에 성공한 것으로 나오게끔 하고 싶은 경우가 있을 것이다. 하지만 테스트를 실행하면 원하는 결과값이 나오지 않았을지라도 코드 자체가 문제 없이 실행되었다면 테스트에 통과되었다고 나오게 된다. 이 때 expect(결과값).toBe(예상값) 를 사용하면 예상했던 결과값과 다른 값이 나올 경우 테스트에 실패했다고 나오게끔 할 수 있다.
터미널에서 npx jest 를 입력해 테스트를 실행하면,
보이는 바와 같이 "블록 생성 테스트" 와 "블록 검증 테스트" 모두 통과하였다.
이러한 방식으로 테스트 코드를 작성해가면서 개발을 진행할 경우, 보다 튼튼한 객체 지향적인 코드 생산이 가능해진다. 앞으로 TypeScript를 사용해 블록 체인을 만들어 가는 과정에서 최소 단위의 기능이 완성될 때마다 테스트 코드를 작성하여 테스트를 진행해 보고자 한다.
테스트를 진행해 봄으로써, 저번 포스팅에서 만들었던 BlockHeader 클래스와 Block 클래스를 사용해 블록을 생성하는 것이 에러 없이 잘 동작한다는 것을 알 수 있었다. 하지만 블록 체인에서는 블록을 생성할 때 아무런 제약 조건 없이 마구잡이로 블록을 생성하지 않는다. 앞서 Block 클래스를 만들 때 속성값으로 넣어줬던 nonce 와 difficulty 를 떠올려보자. 아무 이유 없이 해당 속성을 넣은 것이 아니다. 블록 체인에서는 블록을 생성할 때는 "난이도"라는 개념이 존재한다. 난이도에 의해 특정 목표값이 설정되게 되는데 블록을 생성할 때 만들어지는 hash 값이 목표값보다 낮을 경우에 한해서만 블록이 생성된다. 그렇기 때문에 블록의 hash 값을 만들 때 nonce 값이라는 속성을 추가로 집어 넣게 되고 nonce 값을 하나씩 증가시키면서 대입해보게 된다. 다시 말해, 블록을 생성할 때 난이도에 의해 형성된 목표값 보다 낮은 hash값이 나올 때까지 계속해서 nonce 값을 대입해보는 연산을 수행하는 프로세스를 거치게 되고 목표값 보다 낮은 hash값이 나오게 되었을 때 비로소 블록이 생성되어 블록 체인 상에 연결된다. 이러한 프로세스를 작업증명방식 (PoW, Proof of Work) 이라고 하는데 보다 자세한 내용은 추후에 다뤄보도록 하자.
그래서 우리의 결론은, 지금 까지의 코드만으로는 완벽한 블록 체인이라고 할 수 없다는 것이다. nonce 와 difficulty 에 대한 처리를 추가적으로 해줘야 함은 물론 블록이 생성될 때 코인이 채굴(Mining) 되는 것 , 생성된 블록이 체인 상에 연결되는 것과 같은 프로세스들을 진행해 줘야 한다. nonce 와 difficulty에 대한 처리 작업 (작업증명방식, PoW) 과 마이닝에 관련된 부분은 잠시 뒤로 미뤄두고 다음번 포스팅에서 생성된 블록을 체인으로 연결하는 작업에 대해 먼저 진행해 보도록 하겠다.
다음 글)
2022.06.12 - [BlockChain] - BlockChain - TypeScript로 블록체인 만들기 (3)
'BlockChain' 카테고리의 다른 글
BlockChain - 블록체인 P2P 네트워크 만들기 (1) (1) | 2022.06.14 |
---|---|
BlockChain - TypeScript로 블록체인 만들기 (4) PoW (0) | 2022.06.14 |
BlockChain - TypeScript로 블록체인 만들기 (3) (0) | 2022.06.12 |
BlockChain - TypeScript로 블록체인 만들기 (1) (0) | 2022.06.11 |
BlockChain - JavaScript로 블록 만들기 (0) | 2022.06.08 |