Cute Bow Tie Hearts Blinking Pink Pointer

블록체인

[Typescript] 타입스크립트로 블록체인 블록 검증하기

청포도 에이드 2022. 6. 14. 15:37
728x90

 

중간에 누군가 데이터를 바꿀 수도 있기 때문에 블록이 변조되지 않았는지, 검증이 필요하다.

 

블록을 검증하는 조건은 아래와 같이 총 3가지인데, 이를 모두 만족해야한다.

 

1) 새로운 블럭height 값 = 이전 블럭 height 값 + 1 height가 1 씩 증가

 

2) 이전 블럭의 hash 값 = 새로운 블럭의 previousHash 값

 

3) 블럭의 version, merkleRoot, timestamp, height, previousHash 값을 조합해 hash를 생성했을 때, 블럭의 hash와 값과 동일한지 : 같은 데이터기 때문에 hash도 당연히 같아야한다.

 

1. 타입선언

@types/Failable.d.ts
declare type Result<R> = { isError: false; value: R }
declare type Failure<E> = { isError: true; error: E }
declare type Failable<R, E> = Result<R> | Failure<E>

Failable type은 Result 혹은 Failure 타입을 가지도록 설정해주었다.

 

이를 결정하는 것은 isError의 값이다.

 

만약 Failable type의 isError 값이 true라면  Failure 타입이 되며, 반대로 isError 값이 false라면 Result와 타입이 된다.

 

boolean 값에 따라 타입이 달라지니 실수하지 않도록 조심하기! 

 

2 Block 검증

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

	//여기부터 검증
    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 }
        // 세 개의 조건 모두 만족시 Failable<R> 반환
    }
}

맨 아래부분 inValidNewBlock~ 코드블럭이 검증 코드이다.

 

 

3 테스트 코드

src/core/config.ts
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/block.test.ts
import { Block } from '@core/blockchain/block'
import { GENESIS } from '@core/config'
describe('Block 검증', () => {
    let newBlock: Block

    it('블록생성', () => {
        const data = ['Block #2']
        // newBlock = new Block(genesisBlock, data)
        newBlock = Block.generateBlock(GENESIS, data, GENESIS)
        const newBlock2 = new Block(newBlock, data)
        // 이전 블록을 바탕으로, 새로운 블록을 생성
    })

    it('블록검증 테스트', () => {
        // height: 10 , height: 9
        const isVaildBlock = Block.isValidNewBlock(newBlock, GENESIS)

        if (isVaildBlock.isError) {
            console.error(isVaildBlock.error)
            return expect(true).toBe(false)
        }
        expect(isVaildBlock.isError).toBe(false)
    })
})

 

728x90