dukDukz

[블록체인] 작업증명 / 난이도 조절 본문

웹 개발/블록체인

[블록체인] 작업증명 / 난이도 조절

헤일리_HJ 2021. 9. 8. 13:17

작업증명
- 마이닝, 채굴...


쉽게 말하면 블럭을 생성할 때 쉽게 생성하지 못하도록 하는것.

 

http 인터페이스를 통해 body 영역에 내용 추가해서 블럭 추가했었는데,
요청을 해서 바로 생성 X 
문제를 내고 풀었을때만 (컴퓨터가 자동으로 풀 수 있게끔- 몇번정도 try해서 풀었는지) 블럭 추가할 수 있게.


[block0902.js]

 

difficulty 와 nonce 추가

-> header 내용이 변경된 것

= BlockHeader 를 썼던 부분을 수정해줘야함 (3군데)

 

1. BlockHeader() 에 difficulty 와 nonce 추가

this.difficulty = difficulty    // 문제 난이도
this.nonce = nonce              // 문제 몇번 시도 했는지

해당 코드

더보기
class BlockHeader {
    constructor(version, index, previousHash, time, merkleRoot, difficulty, nonce) {
        this.version = version      
        this.index = index         
        this.previousHash = previousHash
        this.time = time
        this.merkleRoot = merkleRoot
        // 0908
        this.difficulty = difficulty    // 문제 난이도
        this.nonce = nonce              // 문제 몇번 시도 했는지
    }
}

2. createGenesisBlock()

제네시스 블럭 만드는곳에 추가

const difficulty = 0
const nonce = 0
 
const header = new BlockHeader(version, index, previousHash, time, root, difficulty, nonce)

이 부분 추가 및 수정

 

function createGenesisBlock() {
    const version = "1.0.0"
    const index = 0
    const time = 1630907567
    const previousHash = '0'.repeat(64)

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

    const difficulty = 0
    const nonce = 0

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

3. findBlock 함수를 생성해줌

BlockHeader 랑 인자값 똑같이 받음

nextBlock() 에

const difficulty = 0	// 추가
...
const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
// 수정

difficulty 추가하고

BlockHeadr 에 인자값을 줘서 헤더를 만들었던것을 findBlock() 이라는 함수에 넣어서 만드는것으로 바꿔야 한다.

 

해당 코드

더보기
function nextBlock(data) {
    // header
    const prevBlock = getLastBlock()    // 맨 마지막 블럭의 정보를 가져옴
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()
    const difficulty = getDifficulty(getBlocks())    //<- 함수를 만들어야함..

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

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

 

findBlock 함수 생성

function findBlock(version, index, previousHash, time, merkleRoot, difficulty){
    let nonce = 0
    return new BlockHeader(version,index,previousHash,time,merkleRoot,difficulty,nonce)
}

4. findBlock() 안에서 검증처리 - 무한 반복해야함

while (true) {
    if (1 == 1) {
        return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
    }
    nonce ++
}

검증 처리 하는 부분 추가

 

해당 코드

더보기
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
    let nonce = 0
    // 여기서 검증처리 해야함 - 무한 반복 돌거임 - 조건이 맞아야만 빠져나올 수 있도록
    while (true) {
        // 이 상태에서는 block 이 없다.
        // 여기서 createHeaderHash 함수 호출할거임 그래서 인자값으로 nonce 값 줄 수 있음
        let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce)
        console.log(hash);
        console.log('-----------');
        if (hashMatchDifficulty(hash, difficulty)) {       
        // 우리가 앞으로 만들 header의 hash 값의 앞자리 0이 몇개인가?
            return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
        }
        nonce++
    }
}

코드 이해 위해서 sample code
text.js 생성

더보기
const CryptoJs = require('crypto-js')


let a = "0000helloworld!"

// 첫글자 4개가 0000 이 맞냐? - 어떻게 검증해야 할까?
// js string 관련 문법중에 startsWith 이 있음

console.log(a.startsWith("0000"));      // true 가 나온다.
console.log(a.startsWith("0001"));      // false 가 나온다.

/*
    Hash -> SHA256 을 쓸거임
    helloworld 를 암호화 할거임
*/

console.log(CryptoJs.SHA256(a).toString().toUpperCase());   // 소문자에서 대문자까지 바꿔줌

// HASH 값 기준으로 돌릴거임.. 저 반복문

// 내가 첫글자가 0이 4개가 되었을 때 블럭을 생성할 수 있도록 작업할것이다.


1. 현재 hash 값의 결과물은 몇진수일까?? 16진수이다.
2. 16진수와 2진수는 밀접한 관계 (변환이 쉬움)

내 결과물 -> SHA256 으로 변환 (16진수) -> 2진수로 변경할 것이다.
첫글자 16진수 1일때 2진수가 무엇이냐? 이런식으로 16개의 case 를 구해서
SHA256의 hash 값을 2진수로 바꾸는 작업을 할 것이다.
0~F (16진수) -> 2진수 0000 0001 0010 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1111 ... 


a의 hash 값은
1314042ECF8C8A7702AABA1C82D560B5A262FF3E922BB117FA81F2B002FC37B9
0001 0100 
이런식으로 가게 됨
첫글자 2진수로 만들었을 때, 2개가 0이다 하면 0과 1
1개가 0이다 하면, 0~7까지


5. 조건 생성 위해서
utils.js 생성

 

6. hexToBinary() 생성
이 함수의 궁극적인 목표 : 합쳐주기

function hexToBinary(s){
    const lookup ={
        "0" : "0000", "1" : "0001", "2" : "0010", "3" : "0011",
        "4" : "0100", "5" : "0101", "6" : "0110", "7" : "0111",
        "8" : "1000", "9" : "1001", "A" : "1010", "B" : "1011",
        "C" : "1100", "D" : "1101", "E" : "1110", "F" : "1111",
    }
    let rst =""     
    for(let i=0; i<s.length; i++){
        if(lookup[s[i]] === undefined) return null
        rst += lookup[s[i]]
    }

    return rst
}

module.exports = {
    hexToBinary
}

# 한 글자씩 가져오는건 어떻게 해야할까??

console.log(s[1]);

 

이런식으로 하면 된다.



# 총 글자수를 구해오는 방법..

console.log(s.length);


# 한글자씩 검사

for(let i=0; i<s.length; i++){
    console.log(s[i])       //16진수로 리턴해줌
}

 

여기서 한글자씩 리턴해준 값을 2진법으로 바꿔야한다.

- lookup 객체에서 내 인자값을 넣어서 찾아오면 됨

console.log(lookup.F);
이거나
console.log(lookup["F"]);

- 이걸 for 문안에 넣으면?

for(let i=0; i<s.length; i++){
    console.log(lookup[ s[i] ])     //2진수로 변환
}

 

근데 hash 값을 잘못만든경우.
0~F 이외의 다른 문자가 있는경우
undefined 가 나옴
그래서 for 문 안에서 예외처리까지 하는것

for(let i=0; i<s.length; i++){
    if(lookup[s[i]] === undefined) return null
    console.log(lookup[s[i]])    
}

[block.js]

createHash 써야함

function nextBlock(data) {
	const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
}

 

difficulty 추가해서 바꿔줌

function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
    let nonce = 0
    // 여기서 검증처리 해야함 - 무한 반복 돌거임 - 조건이 맞아야만 빠져나올 수 있도록
    while (true) {
        // 이 상태에서는 block 이 없다.
        // 여기서 createHeaderHash 함수 호출할거임 그래서 인자값으로 nonce 값 줄 수 있음
        let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce)
        if (hashMatchDifficulty(hash, difficulty)) {       
        // 우리가 앞으로 만들 header의 hash 값의 앞자리 0이 몇개인가?
            return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
        }
        nonce++
    }
}

findBlock() 에서는 block 이 없어서
함수를 하나 만들어줘야함
createHash 함수를 사용못함

 

createHeaderHash() 함수를 생성

function createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce) {
    let txt = version + index + previousHash + time + merkleRoot + difficulty + nonce
    return CryptoJs.SHA256(txt).toString().toUpperCase()
}

 

 

 if 문 안에 들어가는..
내 hash 2진수로 바꾸고 첫글자 4개가 0인가를 조건으로 넣어줘야함
근데 if 한줄에 넣기에는 양이 많아보임
함수로 빼주자 - hashMatchDifficulty() 생성

// 두 가지 인자값을 받음 hash 와 difficulty(= 0이 몇개인지에 따라 난이도가 달라짐)
function hashMatchDifficulty(hash, difficulty){
    // hash 16진수 -> 2진수
    const hashBinary = hexToBinary(hash)    // 아까 만든 함수에 hash 를 넣어서 16->2 진수로 변경함
    const prefix = '0'.repeat(difficulty)   // difficulty 갯수만큼 0 반복    
    return hashBinary.startsWith(prefix)                 // 얘의 결과 는 boolean 형태
}

 

컴퓨터가 문제를 풀고 블럭을 생성하는..

 


블럭이 많아질수록 난이도가 올라가야함...

difficulty 처리

1분에 하나 이상 만들수 없게 한다거나...
이런식으로... 기준점이 각각 다름..

총 블럭, 총 코인의 개수

시간, 블럭의 개수 정도를 따져서 만들어보자

nextBlock() 부분

const difficulty = getDifficulty(getBlocks())

추가 하고

 

전체 코드

더보기
function nextBlock(data) {
    // header
    const prevBlock = getLastBlock()    // 맨 마지막 블럭의 정보를 가져옴
    const version = getVersion()
    const index = prevBlock.header.index + 1
    const previousHash = createHash(prevBlock)
    const time = getCurrentTime()
    const difficulty = getDifficulty(getBlocks())    //<- 함수를 만들어야함..

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

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

 

[block.js] 에 추가하기

// 블럭의 개수의 따라서 난이도 조정
const BLOCK_GENERATION_INTERVAL = 10    //10초 기준
const BLOCK_ADJUSTIMENT_INTERVAL = 10       // 블럭이 10개가 넘을 때마다 난이도 변경

 

(제네시스 블럭 제외하고 10번째)
10으로 나눈 나머지 값이 0 일때 -> 10의 배수

function getDifficulty(blocks) {
    // 시간에 관련되서 처리
    // 마지막 블럭가져오기
    const lastBlock = blocks[blocks.length - 1]

    if (lastBlock.header.index % BLOCK_ADJUSTIMENT_INTERVAL === 0 && lastBlock.header.index !== 0) {
        // 10으로 나눈 값의 나머지가 0 이 되었을 때 -> 난이도 조정 && 제네시스 블럭이 아니어야함
        return getAdjustedDifficulty(lastBlock, blocks)  
        // 마지막 배열과 내가 전체 갖고있는 블럭을 인자값으로 던져야함
    }
    return lastBlock.header.difficulty      
    // 조건 해당없으면 이전블럭의 난이도와 동일
}

getAdjustedDifficulty 생성

하나의 블럭에 10초 간격으로 생성가능? + 10개마다 난이도 증가
block 10개 단위로 끊는다. - 게시판의 페이징처럼 - 이전의 값

 

[difficulty 조절]

lastblock 의 난이도를 가져오면 좀 힘들어지는게...
-> difficulty + 1 하게되면? 
10번째 블럭이 되어서 난이도 조정해야 하는 상황이라면
11 번째 난이도 증가 시켜야 하는데 이전값 +1 해버리면 안된다.

1  2  3  4  5  6  7  8  9  10
11 12 13 14 15 16 17 18 19 20 

아예 열번째 전에 있는거를 가져오면 된다.
11번은 1번째 블럭 난이도 +1 하라는 얘기

 

function getAdjustedDifficulty(lastBlock, blocks){
    // 하나의 블럭에 10초 간격으로 생성가능? + 10개마다 난이도 증가
    // block 10개 단위로 끊는다. - 게시판의 페이징처럼 - 이전의 값
    // lastblock 의 난이도를 가져오면 좀 힘들어지는게...

    const prevAdjustmentBlock = blocks[blocks.length - BLOCK_ADJUSTIMENT_INTERVAL]      
    // 총 배열 20개 -10 하면 10번째 애를 가져오게됨
    
    // 시간 관련된 녀석
    const timeToken = lastBlock.header.time - prevAdjustmentBlock.header.time           
    // 그러면 시간이 얼마만큼 차이가 나는지 알 수 있다.
}

 

10개 블럭을 만들었을 때 걸리는 시간은 몇 초일까?
- 하나 만들때 10초로 세팅했기 때문에 100초 걸릴것이다.

 

function getAdjustedDifficulty(lastBlock, blocks) {
    const prevAdjustmentBlock = blocks[blocks.length - BLOCK_ADJUSTIMENT_INTERVAL]      
    // 총 배열 20개 -10 하면 10번째 애를 가져오게됨
    
    const timeToken = lastBlock.header.time - prevAdjustmentBlock.header.time           
    // 그러면 시간이 얼마만큼 차이가 나는지 알 수 있다.
    
    const timeExpected = BLOCK_ADJUSTIMENT_INTERVAL * BLOCK_GENERATION_INTERVAL   
    // 예상시간 100초

    // 예상시간보다 빨리만들었으면 난이도 올림
    if (timeToken < timeExpected / 2) {
        return prevAdjustmentBlock.header.difficulty + 1
    } else if (timeToken > timeExpected * 2) {
        // 예상 시간보다 너무 느린데 싶으면 난이도 낮춤
        return prevAdjustmentBlock.header.difficulty - 1
    } else {
        // 예상시간대로 잘 가고 있으면 그대로
        return prevAdjustmentBlock.header.difficulty
    }
}

findBlock 에서
console.log(hash);
console.log('-----------');
이렇게 추가하고

node server 해서
한번 보면
계속 추가하다가


난이도가 4가 되는 시점에 hash 값 앞이 0이 되는
10마다 difficulty +1 이 되는