JavaScript로 블록체인 만들기 #2

JavaScript 로 블록체인 2단계입니다.

Min Seo Park
CAU_CLink
8 min readJun 18, 2019

--

#2 난이도와 채굴 구현

이번 2단계에서는 utils.ts 파일이 추가되었고,
blockchain.ts 파일에 몇가지가 변화가 생겼다.

<그림 2_1 _ 2단계 결과물 4가지 파일들>

p2p.ts 와 main.ts 에는 변화가 없다. utils.ts 에는 16진수를 2진수로 바꿔주는 hexToBinary 라는 constant 가 들어있다.

blockchain.ts 에는 난이도(조절, 조절 주기), PoW(정의, 채굴), timestamp 유효성 검사가 추가되었고 이에 따라 변화하는 block 구조의 변화가 반영되었다. [1]

이번 2단계는

  • 해시값 유효성 검증
  • 변화된 블록구조
  • 유효한 블록찾기 (채굴)
  • 난이도에 대한 합의 (블록생성 주기, 난이도 조절 주기, 난이도 조절 규칙)
  • 타임스탬프 검증

의 순서대로 구현해 나가겠습니다.

개요

이번 챕터에서는 Proof-Of-Work(PoW)를 구현해볼 것이다. 챕터1에서는 cost 없이 블록을 추가할 수 있었다. PoW를 통해 블록이 추가되기 전, 풀어야 할 계산 문제에 대해 소개하고자 한다. 이 퍼즐을 풀어가는 과정이 흔히들 말하는 “채굴” 이다.

PoW가 있으면 블록이 체인에 담기는 주기를 난이도 변화를 통해 제어할 수 있다. 난이도 변화 제어는 블록 채굴 속도가 빠르면 퍼즐의 난이도를 올리고, 채굴 속도가 너무 느리면 난이도는 내리는 형식으로 진행한다.

보통 블록체인 생태계에서 채굴자가 블록 채굴에 성공하면 그에 대한 보상을 받게 된다. 하지만 이번 챕터에서 PoW는 구현되지만, 거래는 구현되지 않을 것이기에 보상 기능 역시 구현되지 않을 것이다.

이번 챕터에서 구현한 전체 코드는 여기서 볼 수 있다.

난이도, 논스 그리고 POW 문제

블록 구조에 2가지 요인을 더할 것이다 difficulty와 nonce이다. 이것들의 의미를 이해하기 위해서, 일단 PoW 퍼즐에 대해서 설명해야 한다.

PoW 퍼즐은 해시의 앞부분에 특정 개수의 0이 달린 블록 해시를 찾는 것이다. 난이도는 해시 값에서 맨 앞에 있는 0의 개수를 뜻한다. 맨 앞의 0들은 해시의 binary format입니다.

아래에 유효한 해시와 유효하지 않은 해시들의 예가 있다.

<그림 2_2 : 난이도와 해당 해시값>

난이도의 관점에서 해시값이 정확하다는 것을 확인하는 코드가 밑에 있다 :

<code 2_1 : 해시값 유효성 검증>

난이도를 만족시키는 해시값을 찾기 위해서, 같은 content를 가진 블록의 다른 해시값도 계산할 수 있어야 한다. 이는 nonce 값을 수정함으로써 이루어진다. 왜냐하면 SHA 256은 해시 함수이고, 블록의 내용이 하나라도 변할 때마다, 해시값은 완벽하게 변하기 때문이다. “채굴”은 기본적으로 블록 해시가 난이도에 맞을 때 까지 다른 논스값을 시도하는 것이다.

difficultynonce를 추가하면, 그 블록 구조는 다음과 같이 보이게 된다 :

<code 2_2 : 변화된 블록 구조>

블록 구조 자체에 변화가 왔으니, 제네시스 블록도 변화시켜야 한다.

유효한 블록 찾기 (채굴)

위에서 설명한 것처럼, 유효한 블록 해시를 찾으려면 논스값을 지속적으로 증가시켜야 한다. 채굴이다. 만족하는 논스값 찾기는 완벽하게 random 하기 때문에, 시간이 꽤 걸리는 경우도 있다.

<code 2_3 : 유효한 블록찾기_채굴>

블록이 채굴되면, chapter 1에서와 같이 네트워크에 전파(broadcast)하면 된다.

난이도에 대한 합의

주어진 난이도에 대한 해시값을 찾고 그 결과를 검증할 수 있는 방법이 구현되었다. 그렇다면 난이도는 어떻게 결정될까? 각 노드가 현 난이도에 대해 합의하는 방법이 존재해야 한다. 이를 위해 네트워크의 현 난이도를 계산하는 방법에 도입될 새로운 몇 가지 규칙들을 소개하려고 한다.

새로운 constant 들을 정의하려고 한다 :

  • BLOCK_GENETRATION_INTERVAL, 블록이 생성되는 주기(얼마나 자주 생성되는가 , 비트코인은 10분 간격)
  • DIFFICULTY_ADJUSTMENT_INTERVAL, 난이도가 네트워크 hashrate 를 조정하는 빈도 (비트코인은 2016블록마다 조정)

우리는 블록 생성 주기를 10초로 하고 난이도 변화 주기는 10블록으로 설정할 것이다. 이 constant 들은 hardcode 되어서 변하지 않는다.

<code 2_4: 블록 생성 주기 & 난이도 조정 주기>

이제 블록의 난이도를 설정하는 방법이 생겼다. 매 10개 블록이 생성될 때마다, 예상 시간보다 오래 걸렸는지 적게 걸렸는지를 확인한다. 예상 시간은 BLOCK_GENETRATION_INTERVAL *DIFFICULTY_ADJUSTMENT_INTERVAL로 계산된다.

소요 시간이 예상보다 2배 이상 혹은 절반 이하로 걸리게 된다면 난이도가 조정되도록 구현한다. 아래와 코드와 같이 구현되었다.

<code 2_5: 블록 생성 시간에 따른 난이도 조절>

타임스탬프 검증

챕터 1에서 구현한 블록체인에서는 타임스탬프가 어떤 역할이나 유효성을 가지지 못하였다. 사실 타임스탬프는 클라이언트가 마음대로 결정할 수 있었다. 이제는 아니다. timeTaken 변수가(앞의 코드에서 볼 수 있듯이) 블록의 타임스탬프에 따라 계산되는 난이도 조정 변수로 적용되었기 때문이다.

거짓 타임스탬프가 선정되어 난이도를 임의로 조정하는 공격을 방지하기 위해 아래의 규칙을 선정한다 :

  • 타임스탬프가 현재 시간으로부터 1분 이내의 차이가 날 때만 블록이 유효하다.
  • 타임스탬프가 이전 블록의 타임스탬프로부터 1분 이내의 차이가 날 때만 블록이 유효하다 .
<code 2_6: timestamp 유효성 확인>

누적 난이도

챕터 1 버전의 블록체인에서는, “가장 긴” 블록체인이 유효한 것으로 선택했다. 이제 난이도가 도입 되었기에 그 규칙을 변경하려고 한다. 이제부터는 “가장 긴” 체인이 “올바른” 체인이 아니라, 누적 난이도가 가장 큰 체인을 유효한 것으로 하려고 한다.

즉, 블록을 생성하는데 가장 많은 리소스(=hashrate * time)가 투입된 체인이 유효한 체인이다.

체인의 누적 난이도를 구할려면 각 블록의 2^difficulty 를 계산하고 그들을 다 더한다. 예를 들어, 5와 11의 난이도를 비교하려고 할 때는 11의 난이도 때 블록을 찾는 것은 5의 난이도 일 때 보다 2^(11–5) = 2⁶ 배 만큼 노력을 해야한다.

아래의 예를 보자. 비록 체인의 길이가 더 짧더라도, “Chain B”가 “옳은” 체인이라고 판단된다 :

<그림 2_3 : 누적>

누적 난이도를 계산할 때는 실제 해시가 아니라 블록의 난이도가 중요하다.

예를 들어, 만약 난이도가 4이고 블록 해시가 000000a34c…인 경우 누적 난이도를 계산할 때는 난이도 6이 아닌 4만 포함된다.[2]

이 속성은 “나카모토 합의(Nakamoto Consensus)”라고 알려져 있다. 사토시가 비트코인을 만들 때 발명해낸 가장 중요한 요인 중 하나이다. 포크가 나면, 채굴자는 그들의 현재 리소스(=hashrate)를 어느 체인에 쏟을지를 골라야만 한다.

블록체인에 포함될 블록을 생산하는 것은 채굴자들의 이익과 관련된 것이기에, 결국 채굴자들은 같은 체인을 선택하도록 유도된다. 블록체인에 저장될 블록을 생성하는 것이 채굴자들의 이득에 관련된 것이기에, 결국 채굴자들이 같은 체인을 선택하도록 유도될 것이다.

결론

POW 의 중요한 특징 중 하나는 풀기는 어렵지만, 검증하기는 쉽다는 것이다. 특정 SHA 256 해시를 찾는 것 역시 좋은 예시이다.

이번 단계에서는 난이도를 구현하였다. 이제는 새로운 블록들을 체인에 추가하기 위해서는 “채굴”을 해야한다. 다음 챕터에서는 거래를 구현해 볼 것이다.

이번 단원의 전체 코드는 여기서 볼 수 있다.

Javascript 로 블록체인 만들기 1편 , 2편 , 3편 , 4편 , 5편

[1] : blockchain.ts 에는 아래의 것들이 추가되었다.

const BLOCK_GENERATION_INTERVAL,
const DIFFICULTY_ADJUSTMENT_INTERVAL,
const getDifficulty,
const getAdjustedDifficulty,
const findBlock,
const getAccumulatedDifficulty,
const isValidTimestamp,
const hasValidHash,
const hashMatchesBlockContent,
const hashMatchesDifficulty

[2] : 000000a34c… 의 경우에 앞에 0이 6개 이지만, 난이도 6인 것은 아니다. 난이도 4 인 것을 만족하려면 앞에 0이 최소 4개가 되어야 한다.

000000a34c… 은 해당 조건을 만족했을 뿐, 실질적인 난이도는 4이기에 누적 난이도를 계산할 때는 6이 아닌 4가 적용된다.

--

--

Min Seo Park
CAU_CLink

Interested in Blockchain, Project Financing and Smart city and Love DJing and EDM