
기존에 작성했던 블록체인 코드를 전면적으로 리팩토링하여 더 견고하고 확장 가능한 구조로 개선했습니다. 이번 글에서는 Block 클래스의 검증 로직과 Chain 클래스의 블록 관리 메커니즘을 중점적으로 다룹니다.
const merkle = require("merkle");
const SHA256 = require("crypto-js").SHA256;
class BlockHeader {
version;
merkleRoot;
timestamp;
height;
difficulty;
nonce;
constructor(_data, _previousBlock) {
this.version = "1.0.0";
const merkleRoot = this.createMerkleRoot(_data);
if (merkleRoot.isError) {
this.merkleRoot = "";
console.error(merkleRoot.msg);
} else {
this.merkleRoot = merkleRoot.value;
}
this.setTimestamp();
this.height = _previousBlock ? _previousBlock.height + 1 : 0;
this.difficulty = 0;
this.nonce = 0;
}
setTimestamp() {
this.timestamp = Date.now();
}
createMerkleRoot(_data) {
if (!Array.isArray(_data) || !_data.length) {
return { isError: true, msg: "Data가 배열이 아닙니다." };
}
return {
isError: false,
value: merkle("sha256").sync(_data).root()
};
}
}
핵심 포인트
class Block extends BlockHeader {
previousHash;
hash;
data;
constructor(_data, _previousBlock) {
super(_data, _previousBlock);
this.previousHash = _previousBlock
? _previousBlock.hash
: "0".repeat(64);
if (this.merkleRoot) {
this.hash = Block.createHash(this);
} else {
this.hash = "";
}
this.data = _data;
}
static createHash(_block) {
let tempStr = "";
const keys = Object.keys(_block);
for (let i = 0; i < keys.length; i++) {
if (keys[i] === "hash" || keys[i] === "data") {
continue;
}
tempStr += _block[keys[i]];
}
return SHA256(tempStr).toString().toUpperCase();
}
static isValidBlock(_newBlock, _previousBlock) {
if (_newBlock.height !== _previousBlock.height + 1) {
return {
isError: true,
msg: "블록의 높이가 이상합니다."
};
}
if (_newBlock.previousHash !== _previousBlock.hash) {
return {
isError: true,
msg: "이전 블록의 해시가 다릅니다."
};
}
if (_newBlock.hash !== Block.createHash(_newBlock)) {
return {
isError: true,
msg: "블록의 해시 생성 중 오류 발생"
};
}
return { isError: false, value: _newBlock };
}
}
module.exports = Block;
검증 로직
1. 블록 높이 검증: 이전 블록 + 1
2. 이전 해시 검증: previousHash 일치 여부
3. 현재 해시 검증: 재계산 후 비교
const Block = require("./block.js");
const merkle = require("merkle");
describe("Block Test", () => {
describe("Data가 배열이 아닐 때", () => {
const data = "a";
const block = new Block(data);
it("merkleRoot가 비어있는가?", () => {
expect(block.merkleRoot).toBe("");
});
it("hash가 비어있는가?", () => {
expect(block.hash).toBe("");
});
});
describe("Data가 배열일 때", () => {
const data = ["a"];
const block = new Block(data);
it("merkleRoot가 정상적인가?", () => {
const merkleRoot = merkle("sha256").sync(data).root();
expect(block.merkleRoot).toBe(merkleRoot);
});
it("hash와 merkleRoot의 길이가 64인가?", () => {
expect(block.merkleRoot).toHaveLength(64);
expect(block.hash).toHaveLength(64);
});
});
});
테스트 결과
const Block = require("../block/block.js");
class Chain {
#chain;
constructor() {
this.#chain = [];
const genesis = new Block([`Genesis Block ${new Date()}`]);
this.#chain.push(genesis);
}
get chain() {
return [...this.#chain];
}
get lastBlock() {
return this.#chain[this.#chain.length - 1];
}
}
설계 특징
#chain)로 외부 접근 차단class Chain {
// ... 이전 코드
createBlock(_data) {
const newBlock = new Block(_data, this.lastBlock);
return this.add2Chain(newBlock);
}
add2Chain(_newBlock) {
const isValid = Block.isValidBlock(_newBlock, this.lastBlock);
if (isValid.isError) {
console.error(isValid.msg);
return null;
}
this.#chain.push(_newBlock);
return _newBlock;
}
}
module.exports = Chain;
메서드 설명
createBlock: 새로운 블록을 생성하고 체인에 추가add2Chain: P2P 환경에서 외부에서 생성된 블록을 체인에 추가 (검증 포함)const chain = new Chain();
chain.createBlock(["Transaction 1"]);
chain.createBlock(["Transaction 2"]);
chain.createBlock(["Transaction 3"]);
const externalBlock = new Block(["External Transaction"], chain.lastBlock);
chain.add2Chain(externalBlock);
console.log(chain.chain);
add2Chain 메서드로 외부 블록 추가 지원