블록체인 코어 로직 리팩토링: Block과 Chain 클래스 재설계

efforthye·2023년 1월 11일

비공개

목록 보기
2/6
post-thumbnail

블록체인 코어 로직 리팩토링: Block과 Chain 클래스 재설계

개요

기존에 작성했던 블록체인 코드를 전면적으로 리팩토링하여 더 견고하고 확장 가능한 구조로 개선했습니다. 이번 글에서는 Block 클래스의 검증 로직과 Chain 클래스의 블록 관리 메커니즘을 중점적으로 다룹니다.


Block 클래스 구현

BlockHeader: 블록의 메타데이터

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() 
        };
    }
}

핵심 포인트

  • 머클 루트 생성 시 배열 유효성 검증
  • 타임스탬프는 밀리초 단위로 관리
  • 블록 높이는 이전 블록 기준으로 자동 증가

Block: 완전한 블록 구조

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);
        }); 
    });
});

테스트 결과

  • 잘못된 데이터 타입에 대한 에러 핸들링 확인
  • 정상적인 블록 생성 및 해시 검증 통과

Chain 클래스 구현

기본 구조

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];
    }
}

설계 특징

  • Private 필드(#chain)로 외부 접근 차단
  • Getter를 통해 새로운 배열 반환 (불변성 보장)
  • Genesis 블록 자동 생성

블록 생성 및 추가

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);

개선된 사항

1. 불변성 보장

  • Private 필드 사용으로 직접 접근 차단
  • Getter에서 새로운 배열 반환

2. 검증 로직 강화

  • 블록 높이, 이전 해시, 현재 해시 3단계 검증
  • 에러 발생 시 명확한 메시지 반환

3. P2P 준비

  • add2Chain 메서드로 외부 블록 추가 지원
  • 검증 로직을 재사용하여 일관성 유지

4. 코드 가독성

  • 명확한 메서드 분리
  • 일관된 네이밍 컨벤션
  • 에러 핸들링 패턴 통일

다음 단계

  • Proof of Work 알고리즘 구현
  • 블록체인 검증 메서드 추가
  • P2P 네트워크 통신 구현
  • 트랜잭션 모델 설계

0개의 댓글