블록체인 Block-Chain - 블록체인 구현 ( 2 )

dev_swan·2022년 6월 14일
1

블록체인

목록 보기
3/36
post-thumbnail
post-custom-banner

블록 생성하기

config.ts

/**  난이도 조정 블록 범위*/
export const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10;

/** 블록 생성 시간 ( 단위 : 분 ) */
export const BLOCK_GENERATION_INTERVAL: number = 10;

/** 생성 시간 단위 60초 */
export const UNIT: number = 60;

export const GENESIS: IBlock = {
    version: '1.0.0',
    height: 0,
    timestamp: 1231006506,
    hash: '0'.repeat(64),
    previousHash: '0'.repeat(64),
    merkleRoot: '0'.repeat(64),
    nonce: 0,
    difficulty: 0,
    data: ['Hello Block'],
};

블록의 난이도를 조절하기 위해 사용할 난이도를 측정할 블록의 범위와 블록 생성 시간 , 생성 시간 단위를 따로 변수로 빼두었습니다.
또한 블록체인에 최초의 블록인 제네시스 블록도 만들어주었습니다.

Block.ts

import { SHA256 } from 'crypto-js';
import merkle from 'merkle';
import hexToBinary from 'hex-to-binary';
import { BlockHeader } from './blockHeader';
import { BLOCK_GENERATION_INTERVAL, DIFFICULTY_ADJUSTMENT_INTERVAL, GENESIS, UNIT } from '@src/core/config'; // 블록 생성 

export class Block extends BlockHeader implements IBlock {
    public hash: string; // 블록의 version, timestamp, merkleRoot, previousHash, height, nonce, difficulty 값을 가지고 구한 블록의 고유한 값
    public merkleRoot: string; // merkleTree의 최상단에 있는 암호화된 값
    public nonce: number; // 블록 생성의 부합하기 위해 hash값을 수정하도록 + 1 씩 증가할 값
    public difficulty: number; // 블록 생성 난이도
    public data: string[]; // 블록의 트랜잭션

    constructor(_previousBlock: Block, _data: string[], _adjustmentBlock: Block) {
        super(_previousBlock);

        this.merkleRoot = Block.getMerkleRoot(_data); 
        this.hash = Block.createBlockHash(this);
        this.nonce = 0;
        this.difficulty = Block.getDifficulty(this, _adjustmentBlock, _previousBlock);
        this.data = _data;
    }

	// 제네시스 블록을 리턴하는 함수
	public static getGENESIS(): Block {
        return GENESIS;
    }

	// 블록 merkleRoot 값을 구할 함수
	public static getMerkleRoot<T>(_data: T[]): string {
        const merkleTree = merkle('sha256').sync(_data);
        return merkleTree.root() || '0'.repeat(64);
    }

	// 블록 해시값을 구할 함수
	public static createBlockHash(_block: Block): string {
        const { version, timestamp, merkleRoot, previousHash, height, nonce, difficulty } = _block;
        const values: string = `${version}${timestamp}${merkleRoot}${previousHash}${height}${nonce}${difficulty}`;
        return SHA256(values).toString();
    }
	// 블록의 난이도를 구할 함수
    public static getDifficulty(_newBlock: Block, _adjustmentBlock: Block, _previousBlock: Block): number {
        // 블록이 10개가 안되면 제네시스 블록의 난이도를 가져와서 사용
        if (_newBlock.height < 9) return 0;
        if (_newBlock.height < 19) return 1;
        if (_newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) return _previousBlock.difficulty;

        // 10번째의 블록의 시간과 1번째 블록의 시간을 빼면 1~10번째까지 블록의 생성시간이 나옵니다.
        const timeTaken: number = _newBlock.timestamp - _adjustmentBlock.timestamp; // 6000
        const timeExpected: number = UNIT * BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL; // 6000

        if (timeTaken < timeExpected / 2) return _adjustmentBlock.difficulty + 1;
        else if (timeTaken > timeExpected * 2) return _adjustmentBlock.difficulty - 1;

        return _adjustmentBlock.difficulty;
    }
	
	// 블록 검증 코드
	public static isValidNewBlock(_newBlock: Block, _previousBlock: Block): Failable<Block, string> {
        if (_previousBlock.height + 1 !== _newBlock.height) {
            return { isError: true, error: '블록 높이가 맞지 않습니다.' };
        }
        if (_previousBlock.hash !== _newBlock.previousHash) {
            return { isError: true, error: '블록 이전 해시값이 맞지 않습니다.' };
        }
        if (Block.createBlockHash(_newBlock) !== _newBlock.hash) {
            return { isError: true, error: '블록 해시가 변경되었습니다.' };
        }
        return { isError: false, value: _newBlock };
    }
}    
  1. Block Class가 extends를 사용하여 BlockHeader Class를 상속받고 implement로 전시간에 생성한 IBlock의 타입을 그대로 참조하였습니다.
  2. 생성자 함수에서 난이도를 구하는데 필요한 _previousBlock(이전블록)_adjustmentBlock(10의 배수 블록), 마지막으로 merkleRoot를 만들 data값을 인자값으로 받습니다.
  3. 블록의 해쉬값을 설정하는 this.hash값을 할당할때 createBlockHash() 인자값에 this를 넣어 해시를 생성할때 필요한 값들을 넣어줍니다.
  4. 블록의 난이도를 설정하는 this.difficulty값을 할당할때는 getDifficulty() 함수가 실행되어 인자값으로 _newBlock(현재 생성한 블록)과 , _adjustmentBlock(10의 배수 블록), _previousBlock(이전블록)을 가지고 10번째 블록의 생성 시간에서 1번째 블록의 생성시간을 빼서 나온 시간이 미리 정의해둔 10개의 블록생성 시간을 비교하여 블록의 생성시간이 미리 정의해둔 시간 / 2 보다 크면 난이도가 쉽다는 뜻이니 난이도를 1 올려주고 반대의 경우라면 난이도가 어렵다는 뜻이니 난이도를 1 내려줍니다.
  5. 블록 검증을 하는 isValidNewBlock 함수를 살펴보면 인자값으로 신규 생성한 블록과 이전 블록을 받고 전시간에 Failable.d.ts에 미리 만들어둔 Failable이란 generic을 사용하여 어떠한 에러가 발생했을 경우에는 isErrortrue이고 error에 두번째 type으로 받은 type으로 된 값을 리턴하고 isErrorfalse일 경우에는 value에 첫번째 type으로 받은 type으로 된 값을 리턴하여 블록을 검증해주었습니다.

블록체인 구현

Chain.ts

import { Block } from '@src/core/blockchain/block';
import { DIFFICULTY_ADJUSTMENT_INTERVAL } from '../config';

export class Chain {
    public blockchain: Block[];

    constructor() {
        this.blockchain = [Block.getGENESIS()];
    }

    public getChain(): Block[] {
        return this.blockchain;
    }

    public getLength(): number {
        return this.blockchain.length;
    }

    public getLatestBlock(): Block {
        return this.blockchain[this.blockchain.length - 1];
    }

    /** 검증된 블록을 블록 배열에 push 할 함수 */
    public addBlock(data: string[]): Failable<Block, string> {
        // 내가 생성한 블록의 높이값을 먼저 가져오기
        // 난이도를 구해야함.
        // 생성시간을 구하는것
        const previousBlock = this.getLatestBlock();
        const adjustmentBlock: Block = this.getAdjustmentBlock(); // 높이가 - 10짜리 블럭
        const newBlock = Block.generateBlock(previousBlock, data, adjustmentBlock);
        const isValid = Block.isValidNewBlock(newBlock, previousBlock);

        if (isValid.isError) return { isError: true, error: isValid.error };

        this.blockchain.push(newBlock);

        return { isError: false, value: newBlock };
    }

    /** 생성 기준으로 블럭 높이가 -10인 블록 가져오기 */
    public getAdjustmentBlock() {
        const currentLength = this.getLength(); // 블록체인의 길이는 높이와 같음
        const adjustmentBlock: Block =
            currentLength < DIFFICULTY_ADJUSTMENT_INTERVAL
                ? Block.getGENESIS()
                : this.blockchain[currentLength - DIFFICULTY_ADJUSTMENT_INTERVAL];
        return adjustmentBlock; // 높이에 해당하는 블록을 반환
    }
}
  1. 앞으로 새로 생성할 블록들을 담을 blockchain에 GENESIS Block을 담아서 배열을 생성해줍니다.
  2. 이렇게 생성한 blockchain 배열에 block.ts에서 생성한 검증된 블록을 추가해주어야합니다.
  3. addBlock 함수로 마지막에 생성된 블록과 10의 배수의 블록을 가져오고 인자값으로 받은 data를 가지고 Block class에 있는 generateBlock 함수를 호출하여 새로운 블록을 생성합니다.
  4. 블록을 생성하고 난후 Block class에 있는 isValidNewBlock 함수를 호출하여 새 블록과 마지막에 생성된 블록을 가지고 검증하는 단계를 거칩니다.
  5. 이때 블록에 에러가 있을경우 error를 리턴하고 에러가 없다면 blockchain 배열 안에 새 블록을 추가하고, 추가한 새로운 블록을 reture해줍니다.
post-custom-banner

0개의 댓글