채굴
•
경제학적 관점에서 채굴은 신규 비트코인을 생성하는 프로세스이다.
•
네트워크 관점에서 채굴은 비트코인 시스템을 안전하게 보호하고 중앙 권력 없이 합의를 이룰 수 있게 해주는 것이다.
•
소프트웨어 관점에서 채굴은 비트코인 트랜잭션 데이터를 모아서 블록이라는 데이터 구조를 완성하는 방법이다.
•
채굴은 약 10분에 한번씩 일어난다.
•
채굴에 성공한 사람은 채굴 인센티브와 블록에 담긴 트랜잭션의 수수료를 받는다.
•
채굴 인센티브는 50비트코인부터 시작하여 210,000블록(약 4년)마다 절반으로 줄어든다.
•
아래 그림은 시간의 흐름에 따른 비트코인의 총량을 보여준다.
#50BTC
start_block_reward = 50 * 10**8
#반감기 블록 수
reward_interval = 210000
def max_money():
current_reward = start_block_reward
total = 0
while current_reward > 0:
total += reward_interval * current_reward
current_reward /= 2
return total
print("Total BTC to ever be created:", max_money(), "Satoshis")
#------ 결과 ------
Total BTC to ever be created: 2100000000000000.0 Satoshis
Python
복사
•
채굴 인센티브는 총 2100만개가 되면 종료된다.
트랜잭션 검증
•
노드들은 채굴에 앞서 트랜잭션을 수집해야 한다.
•
수집된 트랜잭션 중에 검증에 실패한 트랜잭션은 네트워크로 퍼지지 않고 첫 노드에서 폐기된다.
•
아래 체크리스트를 통해 트랜잭션을 검증한다.
◦
트랜잭션의 구문과 구조가 정확한가?
◦
입력값이나 출력값이 비어 있지 않은가?
◦
거래의 크기가 MAX_BLOCK_SIZE보다 작은가?
◦
입력, 출력 금액이 허용된 범위 (1사토시 이상 2100만 BTC 이하)인가?
◦
입력 해시값은 0이 아닌가? (코인베이스 트랜잭션은 전송이 안된다.)
◦
락타임 검증
◦
트랜잭션의 크기가 100바이트 이상인가?
◦
한도 서명수를 넘지 않았는가?
◦
해제 스크립트와 잠금 스크립트가 표준인가?
◦
출력의 UTXO가 존재하는가?
◦
출력이 사용된 거래가 아닌가?
◦
출력이 코인베이스라면 COINBASE_MATURITY(100) 승인을 받았는가?
◦
입력값 금액이 출력값 총액보다 큰가?
◦
수수료가 너무 작은가?
◦
해제 스크립트는 잠금 스크립트를 잘 해제하였는가?
•
트랜잭션의 검증이 완료되면 거래 풀(memory pool, mempool)에 저장된다.
•
네트워크 상에서 이전 거래가 담긴 블록이 도착하면 거래 풀에 있는 트랜잭션을 가져와 새로운 블록을 생성한다.
작업증명
•
작업증명은 블록을 만들 때 블록 헤더의 논스값을 찾는 과정이다.
•
작업증명은 해시알고리즘의 결과를 통해 입력 인자를 알아낼 수 없다는 것을 활용한다.
•
작업증명 방식은 단순 반복 작업을 통해 이루어지고, 검증은 쉽게 할 수 있다.
•
아래의 예시를 통해 작업증명의 방식을 이해할 수 있다.
from __future__ import print_function
import hashlib
import time
text = "I am Satoshi Nakamoto"
target = bytes.fromhex('1000000000000000000000000000000000000000000000000000000000000000')
for nonce in range(20):
input_data = text + str(nonce)
hash_data = hashlib.sha256(input_data.encode()).digest()
print(input_data, '=>', hash_data.hex())
if hash_data < target:
print('nonce : ', nonce)
break
#------ 결과 ------
I am Satoshi Nakamoto0 => a80a81401765c8eddee25df36728d732acb6d135bcdee6c2f87a3784279cfaed
I am Satoshi Nakamoto1 => f7bc9a6304a4647bb41241a677b5345fe3cd30db882c8281cf24fbb7645b6240
I am Satoshi Nakamoto2 => ea758a8134b115298a1583ffb80ae62939a2d086273ef5a7b14fbfe7fb8a799e
I am Satoshi Nakamoto3 => bfa9779618ff072c903d773de30c99bd6e2fd70bb8f2cbb929400e0976a5c6f4
I am Satoshi Nakamoto4 => bce8564de9a83c18c31944a66bde992ff1a77513f888e91c185bd08ab9c831d5
I am Satoshi Nakamoto5 => eb362c3cf3479be0a97a20163589038e4dbead49f915e96e8f983f99efa3ef0a
I am Satoshi Nakamoto6 => 4a2fd48e3be420d0d28e202360cfbaba410beddeebb8ec07a669cd8928a8ba0e
I am Satoshi Nakamoto7 => 790b5a1349a5f2b909bf74d0d166b17a333c7fd80c0f0eeabf29c4564ada8351
I am Satoshi Nakamoto8 => 702c45e5b15aa54b625d68dd947f1597b1fa571d00ac6c3dedfa499f425e7369
I am Satoshi Nakamoto9 => 7007cf7dd40f5e933cd89fff5b791ff0614d9c6017fbe831d63d392583564f74
I am Satoshi Nakamoto10 => c2f38c81992f4614206a21537bd634af717896430ff1de6fc1ee44a949737705
I am Satoshi Nakamoto11 => 7045da6ed8a914690f087690e1e8d662cf9e56f76b445d9dc99c68354c83c102
I am Satoshi Nakamoto12 => 60f01db30c1a0d4cbce2b4b22e88b9b93f58f10555a8f0f4f5da97c3926981c0
I am Satoshi Nakamoto13 => 0ebc56d59a34f5082aaef3d66b37a661696c2b618e62432727216ba9531041a5
nonce : 13
Python
복사
◦
왼쪽은 해시알고리즘(SHA-256)의 입력값이고 오른쪽은 출력값이다.
◦
위 결과에서 변화하는 변수로 사용되는 값 (0~13)를 논스 라고 한다.
◦
작업증명은 특정값 보다 작은 해시코드의 결과를 보여주는 논스를 찾는 것이다.
◦
예를들어 “I am Satoshi Nakamoto13” 의 결과값은 ‘0x10000….’ 값보다 작다.
◦
이 때 논스는 13이다.
◦
실제 비트코인에서는 ‘I am Satoshi Nakamoto’가 아닌 (논스가 제외된) 블록의 헤더가 사용된다.
◦
target(목표값)은 난이도 조절에 따라 변경된다.
목표값(난이도)
•
목표값은 영문으로는 target bits로 블록 헤더에 한 항목이다.
•
bits는 총 8바이트로 구성되며 앞의 두 자리는 지수(exponent)이고 나머지 6자리는 계수(coefficient)이다.
•
목표값을 얻기 위해서는 다음 공식을 따른다.
•
작업증명의 기준값을 변경하여 채굴 확률을 조정하기 때문에 난이도라고 표현하기도 한다.
•
예를들어 277316블록을 보자.
◦
목표값은 bits 항목에 담긴다.
◦
‘1903a30c’ 중 ‘19’는 지수(exponent)이고 ‘03a30c’는 계수(coefficient)이다.
◦
아래 코드를 통해 계산해보면, 결과를 얻을 수 있다.
from helper import little_endian_to_int
bits = bytes.fromhex('1903a30c')
exponent = bits[-1]
coefficient = little_endian_to_int(bits[:-1])
target = coefficient * 256**(exponent - 3)
print('{:x}'.format(target).zfill(64))
#------ 결과 ------
0000000000000000000000000000000000000000a30319000000000000000000
Python
복사
◦
논스를 포함한 블록 헤더의 해시가 해당 숫자보다 작아야 작업증명이 완료된다.
•
난이도(difficulty)는 얼마나 해시값을 얻는것이 어려운지를 직관적으로 보여주는 수치이다.
•
난이도의 비교를 통해서 얼마나 더 어려운지를 쉽게 파악할 수 있다.
•
난이도는 아래의 공식을따른다.
•
난이도는 제네시스 블록에서 1이었지만 80만 번째 블록에서는 53911173001054.59에 이른다.
•
채굴이 약 53조배 어려워졌다는 것을 직관적으로 알 수 있다.
난이도 조절
•
비트코인의 채굴은 약 10분에 한번씩 이루어진다. 통화 발행 빈도는 시간이 흘러도 일정하게 유지될 수 있어야 한다.
•
따라서 비트코인 네트워크에서는 10분의 간격을 충족하기 위해 2016블록마다 조정된다.
•
2016블록이 채굴되는동안 20,160분(2주)을 기준으로 얼마나 차이가 나는지 계산하고 전 주기의 난이도(previous_target)을 기준으로 다음 주기의 난이도(new_target)를 결정한다.
•
새로운 목표값을 도출하는 식은 아래와 같다.
◦
time differential : 나이도 조정 중 마지막 블록의 타임스탬프 - 난이도 주정 중 첫 블록의 타임 스탬프
▪
만약 time differential이 8주보다 크면 8주로 설정한다.
▪
만약 time differential이 3.5일보다 작으면 3.5일로 설정한다.
◦
1209600 는 2주의 기간을 초로 표현한 것이다.
•
이를 통해 블록이 평균 10분마다 생성되게 방향을 맞춘다.
•
off-by-one 버그
◦
time differential이 2016시간이 아닌 2015시간으로 설정되어 있다.
블록 검증
•
네트워크 상에 존재하는 모든 풀 노드는 완성된 블록을 검증한다.
•
비트코인 클라이언트에 checkBlock, checkBlockHeader 함수에 나와있다.
•
검증 항목은 다음과 같다.
◦
데이터 구조가 문법적으로 유효하다.
◦
블록헤더의 해시값이 목표값보다 작아야 한다.
◦
타임스탬프가 2시간 이내다.
◦
블록의 크기가 허용한도 내에 있다.
◦
첫 거래가 코인베이스 거래이다.
◦
모든 트랜잭션이 유효하다.
구현: 작업증명과 난이도
from io import BytesIO
from unittest import TestCase
from helper import (
bits_to_target,
hash256,
int_to_little_endian,
little_endian_to_int,
merkle_root,
)
GENESIS_BLOCK = bytes.fromhex('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c')
TESTNET_GENESIS_BLOCK = bytes.fromhex('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18')
LOWEST_BITS = bytes.fromhex('ffff001d')
class Block:
#... 생략
#난이도를 표현
def target(self):
return bits_to_target(self.bits)
#난이도 계산
def difficulty(self):
lowest = 0xffff * 256**(0x1d - 3)
return lowest / self.target()
#작업 증명 검증
def check_pow(self):
# 블록을 직렬화하고 해시코드를 구함.
h256 = hash256(self.serialize())
# 리틀엔디언으로 변경
proof = little_endian_to_int(h256)
# 목표값보다 해시코드가 작은지 검증함.
return proof < self.target()
#... 생략
Python
복사
구현: 난이도 조절
TWO_WEEKS = 60 * 60 * 24 * 14
MAX_TARGET = 0xffff * 256**(0x1d - 3)
def calculate_new_bits(previous_bits, time_differential):
# time differential이 8주보다 큰 경우
if time_differential > TWO_WEEKS * 4:
time_differential = TWO_WEEKS * 4
# time differential이 3.5일보다 작은 경우
if time_differential < TWO_WEEKS // 4:
time_differential = TWO_WEEKS // 4
# 새로운 난이도를 구하는 공식
new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS
# 최대값 보다 크면 최대값으로 설정한다.
if new_target > MAX_TARGET:
new_target = MAX_TARGET
return target_to_bits(new_target)
Python
복사