이전글에서 다뤘던 블록체인 시스템 구현 부분에서 블록들을 담은 체인을 추가할 것이다. 각 체인안의 메서드는 다음과 같은 기능들을 담고있다.
import Block from "@coreChain/block/block";
import { GENESIS } from "@coreChain/config";
import { Failable } from "@coreChain/interface/failable.interface";
class Chain {
private chain: Block[] = [GENESIS];
private readonly INTERVAL = 10;
// 현재 체인을 반환하는 함수
get() {
return this.chain;
}
// 길이를 반환하는 함수
length() {
return this.chain.length;
}
// 체인에 마지막 블록 반환 함수
latestBlock() {
return this.chain[this.length() - 1];
}
// 블룩 추가 메서드
addToChain(receiveBlock: Block) {
this.chain.push(receiveBlock);
return this.latestBlock();
}
// 블록을 조회하는 메서드
getBlock(callbackFn: (block: Block) => boolean) {
const findBlock = this.chain.find(callbackFn);
if (!findBlock) {
throw new Error("찾은 블록이 없음");
}
return findBlock;
}
// 블록의 높이로 블록을 조회하는 함수
getBlockByHeight(height: number) {
return this.getBlock((block: Block) => block.height === height);
}
// 블록의 헤시로 찾는 함수
getBlockByHash(hash: string) {
return this.getBlock((block: Block) => block.hash === hash);
}
// 10번째 블록들을 찾는 함수 현재 위치에서
getAdjustBlock() {
const { height } = this.latestBlock();
const findHeight =
height < this.INTERVAL
? 1
: Math.floor(height / this.INTERVAL) * this.INTERVAL;
// 10번째들의 블록읠 높이로 블록을 조회해서 블록 반환
return this.getBlockByHeight(findHeight);
}
// 다른 네트워크로 체인을 보낼 때
serialize() {
return JSON.stringify(this.chain);
}
// 다른 네트워크에서 체인을 받을 때
deserialize(chunk: string) {
return JSON.parse(chunk);
}
// 상대방 체인과 본인의 체인을 비교
replaceChain(receiveChain: Block[]): Failable<undefined, string> {
// 본인의 체인과 상대방의 체인을 검사하는 로직
// 실제 네트워크에서는 더 복잡한 로직이 들어가 있겠지만 우리는 체인의 길이를 비교하는 로직을 구현할 것
// 머클루트,해시값,체인 전체검증 등등의 로직이 더 추가되어 있을 건데.
// 중요한건 체인의 길이를 비교하는 것. 롱기스트 체인 룰
// 상대방의 체인의 마지막 블록
const latestReceivedBLock: Block = receiveChain[receiveChain.length - 1];
// 본인의 마지막 블록
const latestBlock: Block = this.latestBlock();
if (latestReceivedBLock.height === 0) {
return {
isError: true,
value: "상대방 네트워크 체인은 마지막 블록이 최초 블록이다",
};
}
if (latestReceivedBLock.height <= latestBlock.height) {
return {
isError: true,
value: "상대방 네트워크 체인과 같거나 더 작다",
};
}
// 상대방의 체인이 내 체인보다 길다는 것
this.chain = receiveChain;
return { isError: false, value: undefined };
}
// 추가할 블록을 찾으면 네트워크에 브로드 케스트를 하고
// 다른 네트워크들은 내 체인과 블록을 받아요
// 블록 검증을 하고
// 체인검증을 하는데
// 다른 네트워크의 체인과 내 체인을 비교해서 긴 체인이 정답
// 다른 네트워크의 체인이 더 길 경우에는 내가 채굴이 늦은 것 (x)
// 다른 네트워크의 체인보다 길어지면 내가 채굴을 더 빠르게 한거=> 보상(o)
// 전체 블록 생성 시점에서
// 이전 -10 번째 블록 구하기
// 현재 높이가 <10:최초블록을 반환하고
// 현재 높이가>10:-10번째 블록을 반환
// 이전 10번째 블록의 생성 시간의 차이를 구해서
// 그 차이가 블록생성 주기보다 빠르면 난이도를 증가
// 느리면 난이도 증가
// 비트코인 기준 생성시간 10분, 10개가 생성되면 100분
// 100분보다 빠르면 난이도 상승, 느리면 하락
getAdjustmentBlock() {
const currentLength = this.length();
// console.log(currentLength)
const adjustmentBlock: Block =
this.length() < this.INTERVAL
? GENESIS
: this.chain[currentLength - this.INTERVAL];
return adjustmentBlock;
}
}
export default Chain;
현재 체인을 반환한다.
현재 체인의 길이를 반환한다.
체인의 마지막 블록을 반환한다.
블록을 받아 추가한다.
콜백함수를 받아 해당 콜백함수의 return 값이 존재할 경우 해당 블록을 찾아준다.
이 콜백들은 하위에서 서술하겠다
블록의 높이로 블록을 조회하며 5번의 getBLock에 콜백함수로 높이 비교를 해주었다.
마찬가지로 콜백함수로 해시비교를 해 해당 해시값의 블록을 반환한다.
10번째 블록의 높이를 반환한다. 만약 10번째까지 없다면 1을 반환한다.
각각 체인을 문자화,JSON화 하여 네트워크로 보내고 받는다.
longest rule을 적용하기 위해 상대방의 체인과 나의 체인을 비교한다.
내가 상대방의 체인보다 길면 에러로 자신의 체인이 길거나 같다고 반환하고 그것이 아니라면 내 체인에 상대방의 체인을 삽입한다.
현재 기분 10번째전 블록을 반환한다. 이를 기준으로 걸린시간을 계산해서 난이도를 조정한다.
이전 포스트에서 작성된 코드를 그대로 복붙해서 설정을 변경해줘야한다.
import type { Config } from "@jest/types";
const config: Config.InitialOptions = {
// 1.모듈 파일 확장자 설정: typescript와 javascript 둘 다 테스트 파일로 지정
moduleFileExtensions: ["ts", "js"],
// 2.테스트 파일 매치 설정: 파일의 이름의 패턴을 설정
// 루트 경로에서 모든 폴더에 모든 파일 이름의 패턴이 test.js or test.ts
testMatch: ["<rootDir>/**/block2.test.(js|ts)"],
// 3.모듈의 이름에 대한 별칭 설정:@core
// 뱔칭으로 지정된 @core를 어떻게 경로를 바꿔줄거냐
// ^@core==@core/**/* 시작하는 별칭은 루트 경로에 src/core의 경로까지
moduleNameMapper: {
// rootDir는 ts_lecture
"^@core/(.*)$": "<rootDir>/20230904/02.Block/src/core/$1",
"^@coreChain/(.*)$": "<rootDir>/20230904/03.chain/src/core/$1",
},
// 4.테스트 환경 설정:node환경에서 실행 시킬거임
testEnvironment: "node",
// 5.자세한 로그 설정 출력: 터미널에 로그들을 더 자세히 출력할지 여부
verbose: true,
// 6.프리셋 설정:typescript 에서 사용랄 jest/ts-jest설정
preset: "ts-jest",
};
export default config;
moduleNameMapper에서 "^@coreChain/(.)$": "<rootDir>/20230904/03.chain/src/core/$1",부분이 추가 됐다.
별칭 하나를 추가해야 새로운 폴더에 작성된coreChain별칭을 사용가능하다.
testMatch: ["<rootDir>/**/block2.test.(js|ts)"],
이 부분은 테스트 코드를 위에서 읽기 시작할 때block2를 와일드카드로 주었을시 첫번째 테스트만 실행되는 버그가 있었다. 때문에 따로 block2파일만을 실행하도록 하였다.
모르는게 산더미로 밀려오지만 나름 기초공부를 해서 잘 따라가고 있다. 시간될 때 마다 복습과 예습을 하여 개념만이라도 익혀야 겠다.... 특히 블록의 헤더구조와 같은 개념적인 부분은 머릿속에서 자연스럽게 떠올릴 정도로 봐야한다.