Cute Bow Tie Hearts Blinking Pink Pointer

블록체인

[Typescript] 타입스크립트로 블록체인 마이닝(채굴) 구현하기

청포도 에이드 2022. 6. 14. 16:43
728x90

채굴을 위해 사용되는 mining 함수는 이전 블록의 해시값, 시간, 거래내역, 난이도(diff)를 인자로 받는다. 그리고 해당 값과 난스, 해시값을 합쳐 블록(block)을 만든다. 블록은 반복적인 해시값 계산을 통해 난이도를 통과하면 최종적으로 반환된다.

 

이렇게 난스를 증가시켜가며 난이도에 맞는 해시값을 발견하는게 바로 '채굴(마이닝)'다.

 

따라서, 마이닝을 위해선 difficulty를 알아야하는데, 이를 위해서 일단 chain을 생성해주어야한다.

 

chain은 쉽게 말하면 배열이다.

difficulty는 지속적으로 변하는데(난이도조정), 기준이 이전 블록이므로, 연결은 반드시 이루어져 있어야한다.

  

코드를 작성하기 전에,

라이브러리를 설치해주는 것을 잊지말자!

 

merkle, crypto-js, hex-to-binary세 개 install 해주기. typescript 같은 경우, typescript 버전도 받아주어야함.

src/core/config.ts
/**
 *  난이도 조정 블록 범위
 */
export const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10

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

/**
 * 블럭한개당 생성되는 시간
 */
export const BLOCK_GENERATION_TIME: number = 60

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

 

 

 

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

export class Chain {
    public blockchain: Block[]

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

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

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

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

    public addBlock(data: string[]): Failable<Block, string> {
        //할것: 내가 앞으로 생성할 블록의 높이값 가져올수 있음?
        // 현재높이값, - block interval=음수값
        //난도 구해야함
        //생성시간을 구하는 것
        const previousBlock = this.getLatestBlock()
        const adjustmentBlock: Block = this.getAdjustmentBlock() // -10 Block 구함
        const newBlock = Block.generateBlock(previousBlock, data, adjustmentBlock)
        const isVaild = Block.isValidNewBlock(newBlock, previousBlock)

        if (isVaild.isError) return { isError: true, error: isVaild.error }
        return { isError: false, value: newBlock }
    }

    /**
     * 생성기준으로 블럭높이가 -10 짜리 구해오기.
     */
    public getAdjustmentBlock() {
        // 현재 마지막블럭에서 - 10 (DIFFICULTY_ADJUSTMENT_INTERVAL)
        const currentLength = this.getLength()
        const adjustmentBlock: Block =
            currentLength < DIFFICULTY_ADJUSTMENT_INTERVAL
                ? Block.getGENESIS()
                : this.blockchain[currentLength - DIFFICULTY_ADJUSTMENT_INTERVAL]
        return adjustmentBlock
    }
}

 

 

 

src/core/blockchain/block.ts
import { SHA256 } from 'crypto-js'
import merkle from 'merkle'
import { BlockHeader } from './blockHeader'
import { BLOCK_GENERATION_INTERVAL, DIFFICULTY_ADJUSTMENT_INTERVAL, GENESIS } from '@core/config'
import hexToBinary from 'hex-to-binary'
/**
 * 객체를 만들기 위함.
 * constructor() 객체 만들려고
 * this = {
 *  version : 'asdfasf'
 *  hash : 'asdfasdfasdfasf',
 *  merkleRoot.:'asdfasdf',
 *  data : ['asdf','asdfasdf','asfdasdf']
 * }
 */
export class Block extends BlockHeader implements IBlock {
    public hash: string
    public merkleRoot: string
    public nonce: number
    public difficulty: number
    public data: string[]

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

        const merkleRoot = Block.getMerkleRoot(_data)

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

    public static getGENESIS(): Block {
        return GENESIS
    }

    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, difficulty, nonce } = _block
        const values: string = `${version}${timestamp}${merkleRoot}${previousHash}${height}${difficulty}${nonce}`
        return SHA256(values).toString()
    }

    public static generateBlock(_previousBlock: Block, _data: string[], _adjustmentBlock: Block): Block {
        const generateBlock = new Block(_previousBlock, _data, _adjustmentBlock)
        // TODO : newBlock은 마이닝이 완료된 블럭
        const newBlock = Block.findBlock(generateBlock)
        return generateBlock
    }

	//여기부터 마이닝
    public static findBlock(_generateBlock: Block): Block {
        // TODO : 마이닝 작업 코드를 넣어야함.
        // _generateBLock Block이 담김
        // hash
        // _generateBlock.hash // -> 2진수
        // hexToBinary(_generateBlock.hash)
        let hash: string
        let nonce: number = 0
        while (true) {
            nonce++
            _generateBlock.nonce = nonce
            hash = Block.createBlockHash(_generateBlock)
            // 16 -> 2
            const binary: string = hexToBinary(hash)
            const result: boolean = binary.startsWith('0'.repeat(_generateBlock.difficulty))
            if (result) {
                _generateBlock.hash = hash
                return _generateBlock
            }
        }
    }
	//난이도 작업
    public static getDifficulty(_newBlock: Block, _adjustmentBlock: Block, _previousBlock: Block): number {
        console.log(_newBlock, _adjustmentBlock)
        if (_newBlock.height < 9) return 0
        if (_newBlock.height < 19) return 1
        if (_adjustmentBlock.height === 0) return 0
        //시간을 구할 때 열 번쨰 배수의 블록일 때만 검사하기를 원함.
        if (_newBlock.height % DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) return _previousBlock.difficulty
        // ~여기가 실행~
        const timeTaken: number = _newBlock.timestamp - _adjustmentBlock.timestamp //결과값은 초로 나옴. ex 2000초
        const timeExpected: number = 60 * BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL //6000
        if (timeTaken < timeExpected / 2) return _adjustmentBlock.difficulty + 1
        // 예상시간보다 너무 빨리 만들어졌는데? 난이도 +1
        else if (timeTaken > timeExpected * 2) return _adjustmentBlock.difficulty - 1
        return _adjustmentBlock.difficulty
        // 예상시간보다 너무 느리게 만들어지는데? 난이도 -1
    }
	//유효성 검증
    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 }
    }
}

findBlock 함수가 마이닝이다.

 

원리는 아래와 같다.

 

블록에서 hash와, data를 제외한 나머지 값들로 hash값을 생성한다.


해당 hash를 2진수로 변환한다.

 

2진수로 변환된 숫자가 몇개의 0으로 시작하는지 확인한다.


0의 갯수가 difficulty 보다 큰지 확인한다.


difficulty 보다 크면 통과!

 

difficulty 보다 작으면 nonce 값을 1 증가 시킨다.

 

그리고 이걸 반복한다.

 

Reference :

https://kong-dev.tistory.com/166

 

[Block-chain/블록체인] chain/difficulty/mining 구현

0.목차 1. 개요 2. POW(작업증명) 3. chain 구현 4. difficulty구현 5. mining 구현 1. 개요 저번 포스팅에서 block의 생성과 구조에 대해 알아보았다. 이번에는 mining(채굴) 기능을 구현해보려 하는데, 선결 과..

kong-dev.tistory.com

https://hajoeun.blog/block-chain-js-1

 

자바스크립트로 블록체인 - mining

자바스크립트로 블록체인을 구현해보자

hajoeun.blog

 

728x90