Block, Header Class에서 생성되는 인스턴스의 인터페이스를 전역으로 declare해 사용할 수 있게 해주었다.
declare interface IBlockHeader {
version: string;
height: number;
timestamp: number;
previousHash: string;
}
declare interface IBlock extends IBlockHeader {
merkleRoot: string;
hash: string;
data: string[];
}
export class Header implements IBlockHeader {
public version: string;
public height: number;
public timestamp: number;
public previousHash: string;
constructor(_previousBlock: IBlock) {
this.version = Header.getVersion();
this.height = _previousBlock.height + 1;
this.timestamp = Header.getTimestamp();
this.previousHash = _previousBlock.hash;
}
static getVersion() {
return '1.0.0';
}
static getTimestamp() {
return new Date().getTime();
}
}
class에서 인터페이스를 적용하려면 implements를 사용하면 된다.
귀찮은 점은 이렇게 굳이 인터페이스를 별도로 규정해서 가져왔더라도 클래스 내부에서 public (혹은 private 등)으로 한번 더 선언해줘야한다는 점이다. 잊기 쉬우니 유의해주자.
import { SHA256 } from 'crypto-js';
import merkle from 'merkle';
import { Header } from './blockHeader';
export class Block extends Header implements IBlock {
public hash: string;
public merkleRoot: string;
public data: string[];
constructor(_previousBlock: Block, _data: string[]) {
const merkleroot = Block.getMerkleroot(_data);
const _header = new Header(_previousBlock);
super(_previousBlock); // 상속받은 클래스의 속성을 모두 가져옴
this.hash = Block.createNewHash(_header, merkleroot);
this.merkleRoot = merkleroot;
this.data = _data;
}
static getMerkleroot(_data: string[]) {
const merkleTree = merkle('sha256').sync(_data);
const merkleroot = merkleTree.root();
return merkleroot;
}
static createNewHash(_header: IBlockHeader, merkleroot: string) {
const str = Object.values(_header).join('') + merkleroot;
return SHA256(str).toString();
}
}
// 기존코드
static createNewHash(_header: IBlockHeader, merkleroot: string) {
const str = Object.values(_header).join('') + merkleroot;
return SHA256(str).toString();
}
// this로 인자를 받는 createNewHashThis함수
static createNewHashThis(_block: Block) {
// values1 방식 ❌
const { data, hash, ...rest } = _block;
const values1 = Object.values(rest).join('');
// values2 방식 ⭕️
const { version, timestamp, merkleRoot, previousHash, height } = _block;
const values2: string = `${version}${timestamp}${merkleRoot}${previousHash}${height}`;
return SHA256(values2).toString();
}
// 생성자함수도 다음과 같이 수정한다.
constructor(_previousBlock: Block, _data: string[]) {
const merkleroot = Block.getMerkleroot<string>(_data);
const _header = new Header(_previousBlock);
super(_previousBlock); // 상속받은 클래스의 속성을 모두 가져옴
this.merkleRoot = merkleroot;
this.hash = Block.createNewHashThis(this);
this.data = _data;
this.nonce = 0;
this.difficulty = 0;
}
jest를 설치했으니 이걸 활용해서 블록이 제대로 생성되는지 확인해보자.
디렉토리 구조는 다음과 같다.
각각의 기능별로 디렉토리를 구분하고 해당 기능에 대한 test파일을 만드는 식으로 하면 소기능 별 확인이 쉬워진다.
import { Block } from '@core/blockchain/block';
describe('Block검증', () => {
// 블록체인 국룰 : 어차피 제네시스 블럭은 하드코딩한 값이다!
const genesisBlock: Block = {
version: '1.0.0',
height: 0,
hash: '0'.repeat(64),
timestamp: 1231006506,
previousHash: '0'.repeat(64),
merkleRoot: '0'.repeat(64),
data: ['Hello Block'],
};
it('블록생성', () => {
const data = ['Block #2'];
const newBlock = new Block(genesisBlock, data);
console.log(newBlock);
});
});
moduleNameMapper
에 설정해주어서 경로의 별칭으로 절대경로를 잡을 수 있다. Block을 import해왔다describe
는 여러가지의 테스트케이스를 하나로 모아주는 역할을 한다. 콜백함수 내에서 여러 테스트케이스에서 공통으로 필요한 전역변수 선언 등을 해준다. 개별적인 테스트케이스는 it
의 콜백함수로 들어간다.npx jest
로 실행해준다. 꼭 테스트파일이 있는 디렉토리가 아니어도 상관없다. 루트디렉토리에서 실행하면 알아서 해당 디렉토리 하위의 영역에 있는 모든 .test.ts 파일을 찾아 알아서 실행해준다.