새 블록 추가, 블록의 연결과 검증

2021. 10. 18. 17:43Blockchain/Blockchain

코드 작성

 

전체 코드

const fs = require('fs'); // file system package
const merkle = require('merkle');
const CryptoJs = require('crypto-js');
const SHA256 = require('crypto-js/sha256');

/*
    사용법
    const tree =
    merkle("sha256")    // 인자값 : 암호화 방법
    .sync([]);          // tree 구조로 변환
    tree.root();        // root를 가져옴
*/

function getVersion(){
    // readFileSync로 내용 가져와서 Json.Parse로 제이슨 형태로 변환 후 비구조할당 처리
    const { version } = JSON.parse(fs.readFileSync("../package.json"));
    // console.log('패키지 : ', package.toString("utf8"));
    // console.log('제이슨 파서 : ', JSON.parse(package).version);
    // console.log('version : ', version);
    return version;
}

function getCurrentTime(){
    // console.log('뉴 데이트 : ', new Date());
    // getTime 메서드로 변환 후 ceil()로 소수점 올림 처리
    // console.log('겟 타임 : ', Math.ceil(new Date().getTime()/1000));
    return Math.ceil(new Date().getTime()/1000);
}

// getVersion();
// getCurrentTime();

// 붕어빵 틀
class BlockHeader{
    constructor(version, index, previousHash, time, merkleRoot, difficulty, nonce){ // Header를 만들 인자값 5개
        this.version = version;             // 1 { version : 1 }
        this.index = index;                 // 2 { version : 1, index : 2}
        this.previousHash = previousHash;   // 3 { version : 1, index : 2, previousHash : 3}
        this.time = time;                   // ...
        this.merkleRoot = merkleRoot;       // ......
    }
    // 안에서 함수 선언도 가능, React에서 했던 Render 등..
    // render(){}
}

class Block {
    constructor(header, body) {
        this.header = header
        this.body = body
    }
}

function createGenesisBlock() {
    // header 만들기 - 5개의 인자값이 필요해! version, index, hash, time, merkle..
    const version = getVersion();
    const time = getCurrentTime();
    const index = 0 // 제네시스 블록은 index=0으로 설정 (최초의 블록이라 우리가 직접 하드코딩)
    const previousHash = '0'.repeat(64) // 제네시스 블록은 이전 hash가 없으므로 자리수만 0으로 맞춰서 제공 
    // body는 배열 형태로 
    const body = ['hello block']
    // body를 가지고 merkletree 값을 구성하기 
    const tree = merkle('sha256').sync(body)
    const root = tree.root() || '0'.repeat(64) // ||사용해서 예외처리

    // header 완성시키기 
    const header = new BlockHeader(version, index, previousHash, time, root)
    // console.log(header)
    // console.log('asdf', new Block(header, body))
    // header 만들어진 결과 가지고 block class에 넣어주기 
    return new Block(header, body)
}

let Blocks = [createGenesisBlock()];

function getBlocks(){
    return Blocks;
}

function getLastBlock(){
    return Blocks[Blocks.length - 1];
}

addBlock(['hello2']);
addBlock(["hello3"]);
addBlock(["hello4"]);

// Block push
// addBlock 함수 실행할때 다음 블럭 만들면서, 인덱스값의 변화 필요 +1
function addBlock(data) {
    const newBlock = nextBlock(data); // Object Block {header, body}

    if (isValidNewBlock(newBlock, getLastBlock())) {
        Blocks.push(newBlock);
        return true;
        // return newBlock;
    }
    return false;
}

// 새 블록의 유효성 검사
function isValidNewBlock(currentBlock, previousBlock){
    // 1. header 검사
    // 1-1. currentBlock에 대한 header, body의 Data Type 검사
    if (!isValidType(currentBlock)){
        console.log(`invalid block ${JSON.stringify(currentBlock)}`);
        return false;
    }
    // 1-2. index 값의 유효성 검사
    if (previousBlock.header.index + 1 !== currentBlock.header.index){
        console.log(`invalid index`);
        return false;
    }
    // 1.3 previousHash 검사
    /*
        previousHash         previousHash
        제네시스 블록 기준 -> 두번째 블록
    */
    if (createHash(previousBlock) !== currentBlock.header.previousHash){
        console.log(`invalid previousBlock`);
        return false;
    }
    
    // 2. body 검사
    /*
        currentBlock.header.merkleRoot -> body [배열]
        currentBlock.body -> merkleTree, root -> result !== currentBlock.header.merkleRoot
        body의 내용 유무 검사
        currentBlock.body.length !== 0 || ( cureentBlock.body로 만든 merkleRoot !== currentBlock.header.merkelRoot )
        currentBlock.body.length !== 0 || ( merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkelRoot )
    */
    // 2.1 body 내용 유무 검사
    if (currentBlock.body.length === 0){
        console.log(`invalid body`);
        return false;
    }
    // 2.2 merkleRoot 검사
    if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot){
        console.log(`invalid merkleRoot`);
        return false;
    }
    return true;
}

// 제네시스 블록 유효성 검사
// 1. 데이터가 바뀐적이 없는지?
// 2. 블록의 모든 배열 확인
function isValidBlock(Blocks){
    if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())){
        console.log(`genesis error`);
        return false;
    }

    // Blocks[0] = 이미 위에서 검증이 완료 된 제네시스 블록 
    let tempBlocks = [Blocks[0]];

    // 제네시스 블록을 제외하고 반복
    for (let i = 1; i < Blocks.length; i++){
        if (isValidNewBlock(Blocks[i], Blocks[i - 1])){
            tempBlocks.push(Blocks[i]);
        } else{
            return false;
        }
    }
    return true;
}

// Type 검사
function isValidType(block){
    return(
        typeof(block.header.version) === "string" &&        // string
        typeof(block.header.index) === "number" &&          // number
        typeof(block.header.previousHash) === "string" &&   // string
        typeof(block.header.time) === "number" &&           // number
        typeof(block.header.merkleRoot) === "string" &&     // string
        typeof(block.body) === "object"                     // object
    );
}

// 다음 블록의 Header와 Body를 만들어주는 함수
function nextBlock(data){
    // header
    const prevBlock = getLastBlock();
    const version = getVersion();
    const index = prevBlock.header.index + 1;
    /*
        이전 해쉬값에
        previousHash = SHA256(version + index + previousHash + timestamp + merkleRoot)
    */
    const previousHash = createHash(prevBlock);
    const time = getCurrentTime();

    const merkleTree = merkle("sha256").sync(data); // 배열
    const merkleRoot = merkleTree.root() || '0'.repeat(64);

    const newBlockHeader = new BlockHeader(version, index, previousHash, time, merkleRoot);
    console.log(new Block(newBlockHeader,data)); // 인자값 data = body
    
    return new Block(newBlockHeader, data);
}

function createHash(block){
    const {
        version,
        index,
        previousHash,
        time,
        merkleRoot,
    } = block.header;
    const blockString = version + index + previousHash + time + merkleRoot;
    const Hash = SHA256(blockString).toString(); // 암호화

    return Hash;
}

module.exports = {
    // getBlocks,
    getLastBlock,
    addBlock,
    getVersion,
}

추가 된 블록의 상태