dukDukz

[블록체인] 블록 추가 | 블록 검증 본문

웹 개발/블록체인

[블록체인] 블록 추가 | 블록 검증

헤일리_HJ 2021. 9. 2. 14:25

1. 블록 추가 (수업 ver)

 

addBlock    - 배열에 push 만 하는 녀석
nextBlock   - 다음 블럭의 header 와 body를 만들어주는 함수

createHash - Hash 를 만들어주는 함수

# 다음 블럭의 header 와 body를 만들어주는 함수
function nextBlock(data){
    # data = body 에 넣어줄 임의값을 받는 매개 변수
    # header 는 5개의 속성값이 필요함 - 그 중 이전해쉬값 이전 인덱스 값이 이전 블록 내용이 필요함
}
[해쉬값]

이전해쉬값의
version + index + previousHash + timestamp + merkleRoot
string 으로 묶은 다음 
previousHash = SHA256(version + index + previousHash + timestamp + merkleRoot)
이렇게 변환
function nextBlock(data) {
    // header
    const prevBlock = getLastBlock()    // 맨 마지막 블럭의 정보를 가져옴
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()

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

    const header = new BlockHeader(version, index, previousHash, time, merkleRoot)
    return new Block(header, data)
}

function createHash(block) {
    const {
        version,
        index,
        previousHash,
        time,
        merkleRoot
    } = block.header
    const blockString = version + index + previousHash + time + merkleRoot
    const Hash = CryptoJs.SHA256(blockString).toString()
    return Hash
}

// Blocks 에 push 하는 녀석
// 다음 블럭을 생성할 형태는 다른 곳에 만들거임
function addBlock(data) {
    const newBlock = nextBlock(data)
    if (isVaildNewBlock(newBlock, getLastBlock())) {
        Blocks.push(newBlock)
        return true;    // 함수를 여기서 끝나게 하고
    } return false;     // 함수를 종료시켜라

}

addBlock(['hello1'])
addBlock(['hello2'])
addBlock(['hello3'])

블럭 하나 추가하는건데 함수 하나로 안되나?
- 나중에 추가할 부분이 있어서 함수를 쪼갠것이다.
함수 하나에 코드가 너무 길면 좋지 않다.

 

 


2. 새 블록 추가 시 검증

[검증 단계]
- type
- data가 잘 맞는지
- data에 대한 ( body 부분에 대한 검증 )
: 검증하는 영역을 어디서 하는게 좋을까?

addBlock() 함수에서 하는게 좋다.
push 를 하기 전에 한 번 체크를 해주는것.
만약 안에 있는 내용이 좀 이상하다 싶으면 push 를 안하면 된다.

 

 

조건식을 만들때 필요한 데이터는?
내가 받은 블럭

if(isValidNewBlock(newBlock, getLastBlock())){
                   내가 앞으로 생성할 값, 이전 블럭값

 

그래서 isVaildNewBlock 함수에서 return 으로 true 를 보내주면
push 가 되는거고 false 면 push 안됨

 


1. 타입검사

: 숫자 외의 string이 들어가면 나중에 문제가 생길 수 있다.
: 인덱스가 string 인 경우 문제생김! ( 이전 index ='1' 이면 다음 index는 +1 해서 '11' 이 되기 때문)
: js 에서는 타입검사가 굉장히 까다로워서 함수를 따로 빼줘야 한다.

// 내가 검사할 블럭만 받으면 됨
function isVaildNewBlock(currentBlock, previousBlock){
    isVaildType(currentBlock)
    return true
}

function isVaildType(block){
    console.log(typeof(block.header.version) === "string")           // string
    console.log(typeof(block.header.index) === "number")             // number
    console.log(typeof(block.header.previousHash) === "string")      // string
    console.log(typeof(block.header.time) === "number")              // number
    console.log(typeof(block.header.merkleRoot) === "string")        // string
    console.log(typeof(block.body) === "object")                     // object
    console.log('-----------------------');
}

 

이렇게 하고 1차 체크 -> true 가 나오면 됨

이거를 이제 조건문으로 바꿔주면 된다.

 

그 전에 

function isVaildType(block){
    console.log(
    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
    )
}

 

이게 true 가 뜨는지 확인해주고
이 조건을 다 만족해야 하니까 && 로 준것이다.

잘 되면 console.log 부분을 return 으로 바꿔준다.
다 true 면 return 으로 true 가 갈것이다.

 

이제 조건문을 걸어준다

function isVaildNewBlock(currentBlock, previousBlock) {
    // false 일때만 유효하지 않다는 것을 알려준다.
    if(!isVaildType(currentBlock)){
        console.log(`invaild block structure ${JSON.stringify(currentBlock)}`);
        return false
    }
    return true
}
${JSON.stringify(currentBlock)

 

: 결과가 오류가 났으면 내 현재 객체를 한번에 확인 할 수 있다.


 

Q. 왜 currentBlock 바로 출력 안하고 ${JSON.stringify}를 하는가?

더보기

${JSON.stringify} 를 안하고 currentBlock 만 하면
[object][object] 가 뜬다. - string 으로 감쌌을때 : 백틱으로 감싸면 이렇게 나옴
그래서 얘 자체를 글자형태로 만들어주고
객체 자체를 호출시키면 데이터 타입이 호출이 된다.
그래서 객체를 보여주고 싶으면 string 으로 바꿔서 보여준다.


2. index 검사

if (previousBlock.header.index + 1 !== currentBlock.header.index) {
    console.log(`invaild index`);
    return false
}

index 값이 유효한지 체크한다.
이전 블럭에서 +1 을 하면 만들 블럭 index 랑 같아야 함


3. Hash 검사

세번째 조건은 previousHash 체크
: 블럭끼리 연결이 잘 되어있는지
어떻게 체크해야 할까?

Hash는 어떻게 만들었는가?
: 해당 블럭의 header 내용을 글자로 합쳐서 SHA256 활용하여 암호화한 결과물


제네시스 블럭 기준으로 두번째 블럭을 체크해보자
그럼 제네시스 블럭은 해쉬값이 있는가?
자기 자신을 해쉬값으로 만들어 놓은적이 없다.
그래서 자기 자신의 헤더를 넣어서 다시 암호화를 진행해야 한다.

Hash 값을 만드는 매서드를 만든적이 있다.
: 새로운 함수를 만들때 createHash() 를 만들었었다.
- 이걸 활용할것이다.

현재 블럭의 해쉬값이랑 이전 헤더에 있는 애들을 갖고 해쉬값을 만들어 본거랑 같은지 확인

(현재 블럭의 해쉬값은 부모의 header 값을 조합해서 암호화 한 녀석이다)

 

 // previousHash 체크
    if (createHash(previousBlock) !== currentBlock.header.previousHash) {
        console.log(`invalid previousBlock`);
        return false
    }

4. Body 영역 / mercel 검사

네번째 조건
이제 body 영역을 체크할 것이다
: 체크하는 이유? - data 에 있는 녀석들은 변하기가 쉬움 그래서 우리는 merkle을 사용하는 것이다


우리는 지금 하나의 블록의 요소가 정확한가 아닌가를 체크하는 작업을 하고 있다.
data의 이동이나 거래는 아직 구현을 하지 않은 상태이다

 

우리가 머클을 만들때 data (배열) 을 넣어서 만들어주고 머클 트리가 탄생되었다.
이제 root() 라는 매서드를 실행하면 최상위 녀석을 갖고 있다.

그래서 먼저 root 가 정확한지 체크해야 한다

즉 내가 현재 만들 블럭의 
currentBlock.header.merkleRoot  -> body [배열]
여기서 한번 더 검증
currentBlock.body -> merkleTree 와 root 를 만들어서 - 여기에 대한 결과물을 위에거랑 비교한다

result !== currentBlock.header.merkleRoot

나만 하면 검증할 필요 없는데 네트워크가 생기면 검사를 해야한다.
지금 내가 갖고 있는 데이터가 정확한지 체크하는 것이다.

그 다음에 body 가 내용이 없지는 않은지 체크하자
내용이 없으면 안됨!!!!

// Body Check - body 가 비어있는 경우
if(currentBlock.body.length === 0){
    console.log(`invalid body`);
    return false
}
// merkel root 가 같지 않은 경우
if(merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot){
    console.log(`invalid merkelRoot`);
    return false
}

 

 


[함수 isVaildNewBlock 의 전체 코드 - 펼쳐보기]

더보기
function isVaildNewBlock(currentBlock, previousBlock) {
    // currentBlock 에 대한 header, body, DataType 을 확인
    if (!isVaildType(currentBlock)) {
        console.log(`invaild block structure ${JSON.stringify(currentBlock)}`);
        return false    // 이 조건이 걸리면 함수 종료 - 그래서 else if 안쓰고 if 문으로 쭉 쭉 써도 된다.
    }

    // index 값이 유효한지 체크한다. - 만들 블럭에서 -1을 하면 이전 블럭 index 와 같아야함
    // 이전 블럭에서 +1 을 하면 만들 블럭 index 랑 같아야 함
    if (previousBlock.header.index + 1 !== currentBlock.header.index) {
        console.log(`invaild index`);
        return false
    }

    // previousHash 체크
    if (createHash(previousBlock) !== currentBlock.header.previousHash) {
        console.log(`invalid previousBlock`);
        return false
    }

    // Body Check - body 가 비어있는 경우
    if(currentBlock.body.length === 0){
        console.log(`invalid body`);
        return false
    }
    // merkel root 가 같지 않은 경우
    if(merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot){
        console.log(`invalid merkelRoot`);
        return false
    }

    return true
}

3. Blocks - 전체 배열 검사

 

전체 Blocks 에 있는 애들을 검증할 함수를 만들어주자


1. 일단 제네시스 블럭이 유효한지, 데이터가 바뀐적이 없는지 확인
2. Blocks 에 있는 모든 배열을 검사를 할것이다. (데이터타입/ index/ Hash/ body/ merkelRoot) Check


현재 검사하는 시점에서 이전값과 이후값을 계속 (배열이 끝날때까지) 검사하면됨

[총 배열 3개]
1번 2번
2번 3번
3번 없음
검사 끝

 

1) 제네시스 블럭이 유효한지?

if(Blocks[0] !== createGenesisBlock()){
        console.log(`GenensisBlock error`)
        return false
}

 

: 오류가 있는게... 객체 { }  와 객체 {  }를 비교할 때 어떻게 뜨냐?
이거는 false 가 뜬다. (js 의 특성)


제네시스 블럭은 객체, createGenesisBlock() 도 객체
결과 :false - 비교자체가 안되서 얘네 둘 다를 string 으로 바꿔줘야 한다.


각각 JSON.stringify() 로 감싸준다.

if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
        console.log(`GenensisBlock error`)
        return false
}

 

이렇게 됨



2. 모든 배열 검사


isVaildNewBlock 로 모든 블럭을 검사해야 한다.
(이전블록 인자값과 현재블록 인자값이 필요함)

tempBlocks 에다가 제네시스 블럭을 일단 담는다 (하드코딩이 되어있고 검사가 끝나서 넣어놓은 거임)
- 전체 블록 중 제네시스 블록은 검사를 하지 않겠다는 의미

for 문 안에 있는 if 문에서
isVaildNewBlock(현재블럭, 이전블럭)
- 여기서 true 면 유효함



근데 isVaildBlock 은 어디다 쓰는거냐??
네트워크가 구성되면 서버가 여러대가 있는데
이 함수는 결국에 특정 서버가 요청하게 되면 여기서 만들었던 코드를 실행시켜야됨
그래서 이 코드는 module 화가 되어있어야 함 - import 혹은 require 할 수 있도록

 



전체 코드

[block0902.js]

더보기
const fs = require('fs')        // file system library 가져오고
const merkle = require('merkle')
const CryptoJs = require('crypto-js')
const SHA256 = require('crypto-js/sha256')


// const tree = merkle("sha256").sync(['aaaa'])
// tree.root() 

// 헤더 만들기
class BlockHeader {
    constructor(version, index, previousHash, time, merkleRoot) {
        this.version = version      //  1 new 생성자를 통해서 this.version 은 {version:1} 이렇게 된다
        this.index = index          // 2 {version:1, index : 2}
        this.previousHash = previousHash
        this.time = time
        this.merkleRoot = merkleRoot
    }
}

class Block {
    constructor(header, body) {
        this.header = header,       //BlockHeader 에서 만든값을 넣겠다. 객체 안에 객체가 들어가게 된다.
        this.body = body
    }
}

let Blocks = [createGenesisBlock()]

function getBlocks() {
    // 나중에 쓸거임 
    return Blocks
}


function getLastBlock() {
    return Blocks[Blocks.length - 1]    // 배열의 마지막 원소를 가져옴   
}



function createGenesisBlock() {
    const version = getVersion()
    const index = 0
    const time = getCurrentTime()
    const previousHash = '0'.repeat(64)

    const body = ['hello block']
    const tree = merkle('sha256').sync(body)
    const root = tree.root() || '0'.repeat(64)

    const header = new BlockHeader(version, index, previousHash, time, root)
    return new Block(header, body)
}

// 다음 블럭의 header 와 body를 만들어주는 함수
function nextBlock(data) {
    // header
    const prevBlock = getLastBlock()    // 맨 마지막 블럭의 정보를 가져옴
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()

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

    const header = new BlockHeader(version, index, previousHash, time, merkleRoot)
    return new Block(header, data)
}

function createHash(block) {
    const {
        version,
        index,
        previousHash,
        time,
        merkleRoot
    } = block.header
    const blockString = version + index + previousHash + time + merkleRoot
    const Hash = CryptoJs.SHA256(blockString).toString()
    return Hash
}

// Blocks 에 push 하는 녀석
// 다음 블럭을 생성할 형태는 다른 곳에 만들거임
function addBlock(data) {
    const newBlock = nextBlock(data)
    if (isVaildNewBlock(newBlock, getLastBlock())) {
        Blocks.push(newBlock)
        return true;    // 함수를 여기서 끝나게 하고
    } return false;     // 함수를 종료시켜라

}

addBlock(['hello1'])
addBlock(['hello2'])
addBlock(['hello3'])

/* etc */

function isVaildNewBlock(currentBlock, previousBlock) {
    // currentBlock 에 대한 header, body, DataType 을 확인
    if (!isVaildType(currentBlock)) {
        console.log(`invaild block structure ${JSON.stringify(currentBlock)}`);
        return false    // 이 조건이 걸리면 함수 종료 - 그래서 else if 안쓰고 if 문으로 쭉 쭉 써도 된다.
    }

    // index 값이 유효한지 체크한다. - 만들 블럭에서 -1을 하면 이전 블럭 index 와 같아야함
    // 이전 블럭에서 +1 을 하면 만들 블럭 index 랑 같아야 함
    if (previousBlock.header.index + 1 !== currentBlock.header.index) {
        console.log(`invaild index`);
        return false
    }

    // previousHash 체크
    if (createHash(previousBlock) !== currentBlock.header.previousHash) {
        console.log(`invalid previousBlock`);
        return false
    }

    // Body Check - body 가 비어있는 경우
    if (currentBlock.body.length === 0) {
        console.log(`invalid body`);
        return false
    }
    // merkel root 가 같지 않은 경우
    if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
        console.log(`invalid merkelRoot`);
        return false
    }

    return true
}

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


function getVersion() {
    const { version } = JSON.parse(fs.readFileSync("../package.json"))
    return version
}

function getCurrentTime() {
    return Math.ceil(new Date().getTime() / 1000)
}




/* 전체 배열 검증 */
function isVaildBlock(Blocks) {
    // 제네시스 블럭이 유효한지?
    if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
        console.log(`GenensisBlock error`)
        return false
    }

    // Blocks 총 3개 있는데
    // 1부터 3까지 반복 - 총 두번이 도는 for 문 - 첫번째는 검사하지 않기 위해 i=1 부터
    let tempBlocks = [Blocks[0]]
    for (let i = 1; i < Blocks.length; i++) {
        if (isVaildNewBlock(Blocks[i], tempBlocks[i - 1])) {
            tempBlocks.push(Blocks[i])
        } else {
            return false
        }
    }
    return true
}

// console.log(Blocks);


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