카테고리 없음

BlockChain Block

tp134679 2025. 5. 13. 19:07

<목차>

1. Block header와 body 

2. GenesisBlcok

3. Block의 연결 previousHash 

 

 

 

 

블록체인의 구조

 

 

 

 

1. Block header와 body 

블록체인의 Block 에도 요청 host처럼 header 와 body로 나뉘어서 정보를 나누어서 저장을 하는 구조를 가집니다. 

이는 머클루트로 데이터만 조회하고 연결되는 hash로 다음 블럭과 연결된 상태만 나타내면 되는데 모든데이터를 조회하면서 비교를 한다면 비용이 늘어나고 검증시간또한 늘어나기 때문입니다!

 

 

블록 구현

 

inferface

구현된 것이 없고 밑그림만이 있는 설계도 로서 표준 약속 규칙입니다. (공통적인 기능을 명시 필수적으로 구현)

일반 메서드 또는 맴버 변수를 가질 수 없고 추상메서드와 상수만 가질수 있습니다.

 

 

Block 에서 해더를 분리해서 inferface를 구현

export interface IBlockHeader {
    version: string;
    height: number;
    timestamp: number;
    previousHash: string;
}

export interface IBlock extends IBlockHeader {
    merkleRoot: string;
    hash: string;
    nonce: number;
    difficulty: number;
    data: string[ ];
}

 

 

class 로 추상화된 블록헤더

import { IBlock, IBlockHeader } from "@core/interface/block.interface";
// implements: 추상화 구조 => IBlockHeader 구조를 지키겠다.
class BlockHeader implements IBlockHeader {
    // ES7 문법
    version: string;
    height: number;
    timestamp: number;
    previousHash: string;
    constructor(_previousBlock: IBlock) {
        this.version = BlockHeader.getVersion();
        this.timestamp = BlockHeader.getTimestamp();
        this.height = _previousBlock.height + 1;
        this.previousHash = _previousBlock.hash || "0".repeat(64)
    }

    static getVersion() {
        return "1.0.0"
    }

    static getTimestamp() {
        return new Date().getTime();
    }
}

export default BlockHeader;

 

class 로 추상화된 블록(BlockHeader)를 상속받은 상태

class Block extends BlockHeader implements IBlock {
    merkleRoot: string;
    hash: string;
    nonce: number;
    difficulty: number;
    data: string[];
    // 이전 블록에 대한 정보도 필요하다.
    constructor(_previousBlock: Block, _data: string[], _adjustmentBlock: Block) {
        // super => 이전 블록 생성자 함수 호출 때문에
        super(_previousBlock);
        this.merkleRoot = Block.getMerkleRoot<string>(_data);
        this.hash = Block.createBlockHash(this);
        this.nonce = 0;
        this.difficulty = Block.getDifficulty(this, _adjustmentBlock, _previousBlock);
        this.data = _data;
    }
    // data: ["tx01"]
    static getMerkleRoot<T>(_data: T[]): string {
        const merkleTree = merkle("sha256").sync(_data);
        return merkleTree.root();
    }

    static createBlockHash(_block: Block): string {
        const {
            version,
            difficulty,
            height,
            merkleRoot,
            nonce,
            previousHash,
            timestamp
        } = _block
        const value: string = `${version}${timestamp}${height}${merkleRoot}${previousHash}${difficulty}${nonce}`;
        return crypto.SHA256(value).toString();
    }

    static findBlock(generateBlock: Block): Block {
        let hash: string
        let nonce: number = 0
        let binary: string;
        while (true) {
            nonce++
            generateBlock.nonce = nonce;
            hash = Block.createBlockHash(generateBlock);
            binary = CryptoModule.hashToBinary(hash);
            if (binary.startsWith("0".repeat(generateBlock.difficulty))) {
                // 채굴이 되었다는거겠지? 해당 스코프 진입 조건은
                generateBlock.hash = hash;
                return generateBlock
            }
        }
    }
    // 블록 추가 메서드
    static generateBlock(_previousBlock: Block, _data: string[], _adjustmentBlock: Block): Block {
        const generateBlock = new Block(_previousBlock, _data, _adjustmentBlock);
        const newBlock = Block.findBlock(generateBlock);
        return newBlock
    }

    static getDifficulty(_newBlock: Block, _adjustmentBlock: Block, _previousBlock: Block): number {
        if (_newBlock.height <= 0) throw new Error("높이가 없습니다!");

        if (_newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) {
            return _previousBlock.difficulty;
        }
        // DIFFICULTY_ADJUSTMENT_INTERVAL개의 블록이 실제로 채굴되는데 걸린 시간
        const timeToken: number = _newBlock.timestamp - _adjustmentBlock.timestamp;
        // 원래 예상했던 시간(예: 블록 하나당 10초 곱하기 주기 개수)
        const timeExpected = BLOCK_GENERATION_INTERVAL * 10 * DIFFICULTY_ADJUSTMENT_INTERVAL;
        if (timeToken < timeExpected / 2) return _previousBlock.difficulty + 1; // 채굴 속도가 너무 빠르면 난이도 +1
        if (timeToken > timeExpected * 2) return _previousBlock.difficulty - 1; // 채굴 속도가 너무 느리면 난이도 -1
    }
}

export default Block;

 

 

2. GenesisBlcok

GenesisBlcok 은 블록체인의 시작점이 되는 블럭으로 이전 블럭이 존재하지 않기 때문에 previousHash 값이 0으로 고정됩니다. 이 블록은 블록체인의 기반이 되는 만큼 보통, 하드코딩되어 있으며, 이후 생성되는 모든 블록은 이 제네시스 블록을 기준으로 연결됩니다.

 

 

export const GENESIS: IBlock = {
    version: "",
    height: 0, //최초의 블럭이기 떄문에 순번이 0
    timestamp: new Date().getTime(),
    hash: "0".repeat(64),
    // 해더와 바디
    previousHash: "0".repeat(64),
    merkleRoot: "0".repeat(64),
    difficulty: 1,
    nonce: 0,
    data: ["tx01", "tx02"]
}

 

IBlock를 타입으로 전달받은 초기 GENESIS블록

 

블록 생성

 

블록은 class의 추상화된 형태로 만들어지는데  

 

let newBlock: Block;
let data = ["tx04"]
newBlock = Block.generateBlock(GENESIS, data)

 

    static generateBlock(_previousBlock: Block, _data: string[]): Block {
        const generateBlock = new Block(_previousBlock, _data);
        const newBlock = Block.findBlock(generateBlock);
        return newBlock
    }

 

        const generateBlock = new Block(_previousBlock, _data);

 

여기서 new를 사용하여 블록을 실체화 하는것 처럼 보이지만 실질적으로는 블럭 후보를 생성하는 코드입니다.

 

블록 후보란?

 

체굴이 되지않은 블록상태 즉 블록으로 만들어지지 않은 데이터로서 

  const newBlock = Block.findBlock(generateBlock);
    static findBlock(generateBlock: Block): Block {
        let hash: string
        let nonce: number = 0

        while (true) {
            nonce++
            generateBlock.nonce = nonce;
            hash = Block.createBlockHash(generateBlock)
            const binary = CryptoModule.hashToBinary(hash)
            if (binary.startsWith("0".repeat(generateBlock.difficulty)))
                // 채굴이 되었다는거겠지? 해당 스코프 진입 조건은
                generateBlock.hash = hash;
            return generateBlock
        }
    }

 

 

여기서 실질적으로 체굴이 완료되지 않는 한 블록으로는 만들어지지 않게 됩니다. 

 

다른 노드가 체굴을 먼저 성공하면 다른노드에서 체굴이 완료되었다는 정보를 네트워크에서 받고 내가 만들던 블록을 폐기 후 새 블록 기준으로 다시 만드는 형태입니다.

newBlock = Block.generateBlock(GENESIS, data)

실질적으로 newBlock이 만들어져야 체굴이 완료된 진짜 블록이 만들어진것을 확인 할 수 있다!

 

 

3. Block의 연결 previousHash 

Block 은 이전 블럭의 hash와 현제 블럭의 preciousHash를 비교하여 검증을 합니다. 

 

데이터 생성

  let data = ["tx01"]
  let data2 = ["tx02"]

 

 

블럭 후보 생성

 

제네시스 의 기본값과 data를 넣어서 블럭 후보를 생성

 const generateBlock = new Block(GENESIS, data);

 

 

블럭 실체화 

 

문제를 해결후 실체화된 블럭 

const newBlock = Block.findBlock(generateBlock);

 

블록의 연결

 

블록의 연결성을 확인하기 위하여 실체화된 블럭을 인자로 블럭후보 생성

  const generateBlock2 = new Block(newBlock, data2);

 

블럭 실체화

 

       const newBlock2 = Block.findBlock(generateBlock2);

 

연결성 확인

 constructor(_previousBlock: Block, _data: string[]) {
        super(_previousBlock);
        this.merkleRoot = Block.getMerkleRoot<string>(_data);
        this.hash = Block.createBlockHash(this);
        this.nonce = 0;
        this.difficulty = 0
        this.data = _data;
    }

 

 

super으로 상속받은 BlockHeader에 가면 인자로 받은 newBlock 의 hash가 previousHash로 들어가는 것을 볼 수 있다.

        this.previousHash = _previousBlock.hash || "0".repeat(64)

 

 

값비교 

        console.log(newBlock.hash);
        console.log(newBlock2.previousHash);

 

             console.log (newBlock.hash)
            cfd571bb4ea9760eef11ec1f13e2f4c85b22a2f231bcebb893664c2f6559db3f

             console.log (newBlock2.previousHash)
            cfd571bb4ea9760eef11ec1f13e2f4c85b22a2f231bcebb893664c2f6559db3f

 

이처럼 블럭과 블럭은 연결성을 나타냅니다.