Categories: 블록체인 글

블록체인 입문 2편: 이더리움 채굴 완전 해부 — Ethash와 GPU의 전쟁

시리즈: 파이썬 개발자가 블록체인을 파헤치다
이번 편은 기술적으로 좀 깊어집니다만 코드를 읽을 수 있다면 훨씬 재밌을 겁니다.

채굴의 본질

1편에서 배운 것처럼, 블록체인은 “다음 블록을 누가 쓸 권리를 갖는가”를 결정하는 메커니즘이 필요합니다. 비트코인과 초기 이더리움의 답이 PoW(Proof of Work) 였어요.

“가장 어려운 해시 퍼즐을 먼저 푸는 사람이 블록을 쓴다”


해시 함수: 채굴의 엔진

채굴의 핵심 도구인 해시 함수부터 이해해야 합니다.

from Crypto.Hash import keccak

k = keccak.new(digest_bits=256)
k.update(b"Hello")
print(k.hexdigest())
# → 1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8

k2 = keccak.new(digest_bits=256)
k2.update(b"Hell0")  # 딱 한 글자 변경
print(k2.hexdigest())
# → 완전히 다른 64자리 값

해시 함수의 핵심 특성 세 가지:

  • 일방향성: 출력으로 입력을 역산 불가
  • 결정론적: 같은 입력 → 항상 같은 출력
  • 눈사태 효과: 입력 1비트 변경 → 출력 완전히 달라짐

PoW의 수학적 구조

채굴의 목표:

Keccak256(블록헤더 + Nonce) < Target값

Target이 작을수록 난이도가 높아져요. 앞자리 0이 많을수록 조건을 만족하기 어렵습니다.

# 채굴 루프 (개념적 의사코드)
nonce = 0
target = 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFF  # 앞 4자리 0

while True:
    candidate = keccak256(block_header + nonce.to_bytes(8, 'big'))
    if int(candidate.hex(), 16) < target:
        print(f"Found! nonce={nonce}, hash={candidate.hex()}")
        break
    nonce += 1
    # nonce 공간(2^64) 소진 시 타임스탬프 변경 후 재시도

지름길이 없어요. 무조건 하나씩 시도해야 합니다. 그래서 더 빠른 하드웨어가 절대적으로 유리해요.


Ethash: ASIC을 막으려는 이더리움의 선택

비트코인 채굴이 암호화폐 채굴에 최적화된 반도체 칩인 ASIC(Application-Specific Integrated Circuit)에 의해 장악되자, 이더리움은 “연산속도보다 메모리 대역폭을 병목으로 만들자” 는 아이디어로 Ethash를 설계했습니다.

Ethash는 3단계 파이프라인으로 구성됩니다.

Stage 1: Cache 생성 (약 16MB, CPU RAM)

def make_cache(epoch):
    seed = keccak512(epoch.to_bytes(32, 'big'))
    cache = [seed]

    # 선형 체인 구조로 생성 (병렬화 불가)
    for i in range(1, CACHE_SIZE // 64):
        cache.append(keccak512(cache[-1]))

    # 3회 반복 혼합으로 순서 의존성 강화
    for _ in range(3):
        for i in range(len(cache)):
            v = cache[i][0] % len(cache)
            cache[i] = keccak512(
                xor(cache[(i-1) % len(cache)], cache[v])
            )
    return cache

Cache는 매 30,000 블록(Epoch)마다 새로 만들어져요.

Stage 2: DAG 생성 (1~5GB, GPU VRAM)

Cache로부터 훨씬 큰 데이터셋(DAG)을 만듭니다.

def make_dag_item(cache, index):
    mix = cache[index % len(cache)].copy()
    mix[0] ^= index
    mix = keccak512(mix)

    # 256회 cache 무작위 참조
    for j in range(256):
        cache_idx = fnv(index ^ j, mix[j % 16]) % len(cache)
        mix = fnv_mix(mix, cache[cache_idx])

    return keccak512(mix)

# DAG 전체 (수억 개 항목) → GPU VRAM에 적재
DAG = [make_dag_item(cache, i) for i in range(DAG_ITEMS)]

DAG 크기는 Epoch마다 증가했습니다:

Epoch 0   → DAG ~1.0 GB
Epoch 200 → DAG ~2.5 GB
Epoch 400 → DAG ~4.7 GB  ← 4GB VRAM GPU 탈락

Stage 3: Hashimoto (실제 채굴 루프)

def hashimoto(header, nonce, dag):
    seed = keccak512(header + nonce)
    mix = seed * 2  # 128바이트로 확장

    # 64회 DAG 무작위 접근 (핵심!)
    for i in range(64):
        dag_index = fnv(seed[0] ^ i, mix[i % 32]) % (len(dag) // 2)
        dag_chunk = dag[dag_index*2] + dag[dag_index*2 + 1]  # 128바이트
        mix = fnv_mix(mix, dag_chunk)

    # mix 압축 → 최종 해시
    compressed = [fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
                  for i in range(0, 32, 4)]
    return keccak256(seed + compressed)

왜 VRAM이 필수인가?

64회 반복마다 DAG의 무작위 위치 128바이트에 접근합니다. 패턴을 예측할 수 없으니 캐시 히트가 불가능하고, DAG 전체가 항상 메모리에 올라와 있어야 합니다. HDD/SSD로 DAG를 접근하면 1,000배 이상 느려집니다.


GPU가 CPU를 100배 앞선 이유

CPU:  복잡한 분기 처리에 최적화된 범용 코어 8~32개
GPU:  단순 반복 연산에 최적화된 CUDA 코어 4,000~10,000개

해시 계산 = 단순하지만 엄청나게 많이 → GPU 압승

실제 CUDA 커널 구조(개념):

__global__ void ethash_search(uint64_t start_nonce, uint64_t target,
                               uint32_t* dag, uint8_t* header,
                               uint64_t* result) {
    // 각 스레드가 서로 다른 nonce를 독립적으로 시도
    uint64_t nonce = start_nonce + blockIdx.x * blockDim.x + threadIdx.x;

    uint64_t hash = hashimoto_cuda(header, nonce, dag);

    if (hash < target) {
        atomicExch((unsigned long long*)result, nonce);
    }
}

난이도 폭탄 (Difficulty Bomb)

이더리움 코드에는 처음부터 PoS 전환을 강제하는 장치가 내장돼 있었습니다.

def get_difficulty(block_number, parent_difficulty):
    # 일반 난이도 조정
    base = normal_adjustment(block_number, parent_difficulty)

    # 폭탄 난이도 (기하급수적 증가)
    bomb_delay = 3_000_000  # EIP로 여러 번 연장됨
    fake_block = max(0, block_number - bomb_delay)
    bomb = 2 ** (fake_block // 100_000 - 2)

    return base + bomb

폭탄이 터지기 시작하면 블록 생성 시간이 점점 늘어나 채굴이 사실상 불가능해집니다. 개발자들이 PoS 전환을 미룰 수 없게 만드는 코드 수준의 강제 장치였죠.


The Merge: 이더리움의 대전환 (2022년 9월)

PoW 채굴 폐지 → PoS 스테이킹으로 전환

전력 소비: 연 ~78 TWh (네덜란드 수준) → -99.95% 감소
ETH 발행량: 대폭 감소 (검증자 보상만 남음)
보안 모델: 해시파워 → 예치된 ETH로 교체

현재는 32 ETH를 스테이킹하면 검증자(Validator)가 될 수 있고, 연 2.8~3.5%의 스테이킹 보상을 받습니다.


핵심 정리

개념설명
PoW해시 퍼즐을 먼저 푸는 사람이 블록 생성권 획득
EthashCache → DAG → Hashimoto 3단계 파이프라인
DAGGPU VRAM에 올려야 하는 1~5GB 데이터셋
난이도 폭탄PoS 전환을 강제하기 위한 기하급수적 난이도 증가 장치
The Merge2022년 PoW → PoS 전환, 전력 99.95% 절감

다음 편 예고

3편에서는 채굴로 실제로 돈이 됐는지 계산해보고, 채굴 풀이 내부적으로 어떻게 작동하는지 Stratum 프로토콜 수준에서 파헤칩니다.


Ethash 코드가 더 궁금하다면 github.com/ethereum/ethash 에서 실제 C++ 구현을 볼 수 있습니다!

Recent Posts

블록체인이 진짜 파는 것: “나는 못 고친다”

체인 위의 데이터 — 3부작 중 3편 · ChainLab 앞의 두 편은 배관에 관한 것이었습니다…

2개월 ago

데이터를 소비하는 것이 스마트컨트랙트일 때

체인 위의 데이터 — 3부작 중 2편 · ChainLab 1편에서 우리는 하나의 재정의에 도달했습니다. 블록체인은…

2개월 ago

무료 데이터를 블록체인에 얹어 판다는 것

체인 위의 데이터 — 3부작 중 1편 · ChainLab 솔깃한 아이디어가 하나 있습니다. 부동산 실거래가…

2개월 ago

한국에선 왜 아직? — 규제 이야기, 쉽게 (5/5)

기술은 세계 최고 수준인데, 왜 우리나라에선 AI 결제가 아직 잘 안 굴러갈까요? 답은 '실력'이 아니라…

2개월 ago

에스크로 — 돈을 안전하게 맡기는 금고, 그런데 왜 어려울까 (4/5)

중고거래 안전결제, 다들 써보셨죠? AI 결제에도 이게 필요합니다. 문제는, 생각보다 훨씬 까다롭다는 거예요. 중고거래를 떠올려…

2개월 ago

AP2 & MPP — ‘미리 허락받는’ 결제 vs ‘탭 열어두는’ 결제 (3/5)

앞 편의 x402가 자판기였다면, 이번 두 방식은 '용돈 한도'와 '술집 외상장부'에 가깝습니다. 앞 편 복습:…

2개월 ago