BlockChain>작업증명/마이닝

YU YU·2021년 9월 8일
0

경일_BlockChain

목록 보기
4/24
post-thumbnail

01. 작업증명(PoW)이란?


작업증명이란 Power of Work의 방식으로 이루어지는 합의 알고리즘이다. 비트코인에 쓰이고 있으며, 많은 일을 한 사람이 많은 것을 가져간다는 알고리즘이다. 가장 빨리 일을 한 사람이 가져가는 방식이다.

01-1. 작업증명의 장점은?

작업증명 방식의 장점은 '보안성'에 있다. 작업증명방식은 채굴하는 과정에 시간과 돈이 많이 들기에 해킹의 위험성이 적다.

01-2. 작업증명의 단점은?

가장 큰 것은 '느리다'는 점이다. 모든 노드들ㄹ이 하나의 블록을 캐려고 노력하고 또한 채굴 방식도 난이도 조절에 따라서 되는 것이라 유의미한 결과물을 가져다주지 않는다.


02. 작업증명의 방식


작업증명방식의 채굴의 '난이도'는 '난이도'와 '시간'에 따라서 따라서 결정되도록 해야한다. 예상 시간보다 채굴의 속도가 너무 느리면 난이도를 하향조절해야하고, 너무 빠르면 난이도를 상향조절 해야한다.

02-1.작업 증명의 알고리즘

  1. 예상 채굴 시간난이도 조절 단위수를 변수로 설정한다.
  2. 블록의 헤더에 'difficulty'와 'nonce'를 추가한다.
  3. 헤더부분을 SHA256 암호화한 결과를 2진수로 변환하기
  4. 2진수로 변환한 결과에 따른 조건을 설정한다.
    (여기에서는 앞자리가 특정갯수만큼 0으로 시작되는 조건을 사용)
  5. while문을 통해 그 결과가 도출되면 블록을 생성한다. 조건을 만족하지 않으면 한 차례당 nonce를 증가시키고 조건을 만족할때까지 실행시키도록 한다.
    (여기서 noncetime이 바뀌면서 암호화된 결과가 변한다.)
  6. 그리고 난이도 조절 단위 수가 되었을 때(여기서는 10번재 블록) 시간을 보고, 적절하면 난이도를 유지하고 오래걸리면 난이도를 감소시키고, 빠르면 난이도를 증가시킨다.

1. 예상 채굴 시간난이도 조절 단위수를 변수로 설정한다

const BLOCK_GENERATION_INTERVAL = 10;
const BLOCK_ADJUSTMENT_INTERVAL = 10

BLOCK_GENERATION_INTERVAL은 하나의 블럭당 예상 채굴 시간이다.
또한, BLOCK_ADJUSTMENT_INTERVAL난이도 조절 단위 수이다. 난이도 조절 단위 수란 일정 갯수의 블럭이 생성되었을 때 난이도를 조절는 메커니즘의 기준이 되는 갯수를 뜻한다.

2. 블록의 헤더에 'difficulty'와 'nonce'를 추가한다.

difficulty는 난이도를 뜻하고, nonce는 조건을 맞추기위해 함수가 몇번 반복되었는지를 나타낸다. 이전에 만들었던 createGenesisBlockHeader에 추가해준다.

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
        this.difficulty = difficulty
        this.nonce = 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)
}

function nextBlock(data){
    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 = new findBlock(version,index,previousHash,time,merkleRoot,difficulty);
    return new Block(header,data)
}

nextBlock에 difficulty는 함수를 이용해서 난이도 조절을 할 것이다.

3.헤더부분을 SHA256 암호화한 결과(16진수 64자리)를 2진수로 변환하기

SHA256으로 16진수로 암호화 한 결과를 2진수로 변환하여 조건을 만들려고 한다. 16진수는 2진수로 변환하기가 쉽다. 16진수 한자리는 4자리의 2진수로의 변환이 쉽다.
javascript에서 구현하는 방법은 2가지가 있다.

  1. 16진수를 나타내는 2진수의 모든 경우의 수를 모두 객체에 담아 찾는것
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;
}
  1. 내장함수를 이용해서 16진수를 2진수로 변환하는 것
function hexToBinary(s){
	let rst ="";
	for(let i = 0; i < s.length; i++){
    		if(parseInt(s[i],16)===NaN) return null
        rst +=((parseInt(s[i],16).toString(2).padStart(4,'0')))
        }
        return rst;
}    

4.조건을 정한다.

findBlock: while을 통해서 createHeaderHash의 값이 조건에 맞을 때까지 무한히 반복한다. 조건문이 반복할 때마다 nonce값이 증가한다.

function findBlock(version,index,previousHash,time,merkleRoot,difficulty){
    let nonce = 0
    while(true){
        let hash = createHeaderHash(version,index,previousHash,time,merkleRoot,difficulty,nonce)
        if(hashMatchDifficulty(hash,difficulty)){//우리가 만들 header의 hash값의 앞자리 0이 몇개인가....
            //이곳에서 createHeaderHash 함수 호출할 것임
            return new BlockHeader(version,index,previousHash,time,merkleRoot,difficulty,nonce);
        }
        nonce++;
    }
}

createHeaderHash: 헤더의 값에 nonce값을 추가해서 모두 더한 string값을 가지고 SHA256 암호화를 한 결과를 내보낸다.

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

hashMatchDifficulty: difficulty를 이용해 만든 조건을 만족하는지 hash값과 대조해보고 조건에 해당되면 블록을 생성한다.

function hashMatchDifficulty(hash,difficulty){
    const hashBinary = hexToBinary(hash);
    const prefix = "0".repeat(difficulty);
    
    //높으면 높을수록 조건을 맞추기가 까다로워짐(nonce값과 time값이 바뀌면서 암호화값이 달라진다.)
    return hashBinary.startsWith(prefix)

}

5.난이도 조절 단위 수가 되었을 때(여기서는 10번째 블록) 시간을 보고, 적절하면 난이도를 유지하고 오래걸리면 난이도를 감소시키고, 빠르면 난이도를 증가시킨다.

getDifficulty : 난이도 조절 단위 수 만큼 나누고 나머지가 0이라면 난이도 조정하는 getAdjustedDifficulty 함수를 실행시킨다.

function getDifficulty(blocks){

    const lastBlock = blocks[blocks.length-1];
    if(lastBlock.header.index % BLOCK_ADJUSTMENT_INTERVAL ===0 && lastBlock.header.index!=0){
        //난이도 조정 코드
        return getAdjustedDifficulty(lastBlock,blocks)
    }
    return lastBlock.header.difficulty
}

getAdjustedDifficulty: 지금 블록에서 난이도 조절 단위 수만큼의 전 블록과의 time 즉, 생성시간을 비교해서 자신의 예상 시간보다 느리거나 빠르면 난이도를 조절한다.
적당하면 난이도가 유지되고 블럭의 생성시간이 느리면 난이도를 낮추고, 빠르면 난이도를 높인다.

function getAdjustedDifficulty(lastBlock,blocks){
    const preAdjustmentBlock = blocks[blocks.length - BLOCK_ADJUSTMENT_INTERVAL];
    //시간 관련
    const timeToken = lastBlock.header.time - preAdjustmentBlock.header.time;
    const timeExpected = BLOCK_ADJUSTMENT_INTERVAL*BLOCK_GENERATION_INTERVAL;
    if(timeExpected>timeToken/2){
        return preAdjustmentBlock.header.difficulty+1;
    } else if(timeExpected<timeToken*2){
        return preAdjustmentBlock.header.difficulty-1;
    } else{
        return preAdjustmentBlock.header.difficulty
    }
}



03. 마이닝


$ curl http://localhost:3001/blocks
를 zsh창에 입력하여 제네시스 블록을 만든다.

그리고 $ curl -X POST -H "Content-Type:application/json" -d "{\"data\":[\"데이터내용들\"]}" http://localhost:3001/mineBlock을 입력해 블록을 생성한다. 여기서는 10개까지는 쉽게 만들어질 것이다. 그런데 이후로는 난이도가 조정되면서 블록을 점차 만들기 힘들어진다.


난이도가 조정되면서 145번째 블록은 nonce가 1538번 즉, while문이 1538번 반복되었다는 뜻이다. 점점 블록을 만들기가 힘들어진다. 이렇게 난이도를 조정할 수 있다.

profile
코딩 재밌어요!

0개의 댓글