3장 · 트랜잭션
주소가 생겼다. 개인키로 서명도 할 수 있다. 이제 "철수가 영희에게 1 ETH 보낸다"는 메시지를 네트워크에 뿌리면 끝일까? 막상 구현해보니 그렇지 않았다. 유효한 서명이 담긴 메시지를 복사해서 반복 전송하면, 노드는 이게 중복 요청인지 알 방법이 없어 여러 번 처리할 수 있다. 중간에 누군가 금액만 바꿔치기할 수 있다. 노드마다 메시지를 다르게 해석하면 합의가 깨진다.
❓ 이 챕터의 질문
🔍 비트코인(UTXO) vs 이더리움(계정 모델) 비교
비트코인은 "계좌 잔액"이라는 개념이 없어요. 이전 거래에서 받은 사용되지 않은 출력(UTXO, Unspent Transaction Output) 을 통째로 입력에 넣고, 새 출력으로 쪼개는 방식이에요. 현금에 비유하면, 이더리움이 통장 잔액에서 차감하는 방식이라면, 비트코인은 5만원권을 내고 거스름돈을 받는 방식이에요.
| 비트코인 (UTXO) | 이더리움 (계정 모델) | |
|---|---|---|
| 기본 단위 | 사용되지 않은 출력(UTXO)을 입력으로 소비 | 계좌 잔액에서 차감 |
| 재사용 방지 | 한 번 쓴 UTXO는 소멸 → 구조적으로 재사용 불가 | Nonce 카운터 |
| 수수료 | 입력 합 − 출력 합 | Gas 사용량 × 단가(Base Fee + 팁) |
| Data 필드 | 없음 (제한적인 스크립트만 지원) | 있음 → 4장 스마트 컨트랙트의 기반 |
| 직렬화 | 비트코인 고유 포맷 | RLP |
이 챕터가 제기하는 세 가지 문제(복사 공격, 바꿔치기, 파싱 불일치)는 어느 블록체인이든 풀어야 하는 보편적인 문제예요. 비트코인도 같은 문제를 풀었고, 푸는 방식이 다를 뿐이에요. 재사용은 UTXO 소멸로, 바꿔치기는 전체 서명으로, 파싱은 고유 직렬화 포맷으로요.
트랜잭션: 세 문제를 한 번에 해결하는 표준 묶음
블록체인은 이 세 가지 문제를 트랜잭션이라는 표준 데이터 구조로 한 번에 해결했어요. 모든 필드를 하나의 바이트 덩어리로 포장해서 묶고, 그 전체에 서명을 씌우는 거예요.
각 데이터 앞에는 "이 데이터는 몇 바이트짜리"라는 길이 정보(RLP, Recursive Length Prefix)를 붙여요. 포장 방식이 이렇게 표준화되니 어떤 노드든 똑같이 해석하죠. 그리고 포장된 전체에 서명을 씌우니까, 내용이 한 바이트라도 바뀌면 서명 검증이 곧바로 실패해요.
| 필드 | 역할 | 막는 공격 |
|---|---|---|
| From / To / Value | 보내는 주소, 받는 주소, 금액 | RLP 인코딩으로 파싱 통일 |
| Nonce | 내 지갑의 N번째 요청 | 동일 Nonce 재사용 불가 → 복사 공격 차단 |
| Gas Limit / Max Fee | 실행 한도 + 낼 수수료의 상한 | 다음 섹션에서 설명 |
| Data | 스마트 컨트랙트 호출 데이터 | 단순 송금이면 비어 있음 |
| Signature (v, r, s) | 전체 필드에 개인키로 서명 | 한 글자라도 바뀌면 서명 실패 → 바꿔치기 차단 |
트랜잭션 처리 과정
서명된 트랜잭션이 네트워크에 들어오면 어떤 일이 벌어질까요?
1단계: 수집 블록 생성 노드가 네트워크에 떠돌아다니는 트랜잭션들을 Mempool(메모리 풀)에 수집해요. Mempool은 아직 블록에 포함되지 않은 트랜잭션들의 대기실이에요.
2단계: 수수료 정렬 검증자에게 주는 팁(Priority Fee)이 높은 순서로 처리 우선순위를 정해요. 빨리 처리받고 싶으면 팁을 더 내는 구조예요. (수수료가 어떻게 구성되는지는 5장에서 자세히 설명해요.)
3단계: 선언 트랜잭션을 실행하기 전에 Gas fee를 확인하고 송신 계정의 Gas를 확보해요.
4단계: 실행 트랜잭션 내용에 따라 자산을 이동하거나 스마트 컨트랙트를 실행해요. 이 과정에서 Gas를 소모해요.
5단계: 잔여 반환 실행 후 남은 Gas는 송신자에게 돌려줘요. Gas가 부족하면 실행이 취소(revert)되고 상태는 원래대로 돌아가요. 단, 소모된 Gas는 돌려주지 않아요.
6단계: 블록 추가 이상 없는 트랜잭션을 블록에 담아요. 1블록 안에 담을 수 있는 총 Gas 한도(Block Gas Limit)를 초과할 수 없어요.
그래서 왜 "블록체인"인가
방금 6단계에서 트랜잭션이 "블록"에 담겼어요. 1장부터 미뤄온 질문을 여기서 풀게요. 블록이 뭐고, 왜 이름이 블록체인일까요?
블록은 트랜잭션 묶음이에요. 일정 시간(이더리움은 약 12초)마다 그동안 모인 트랜잭션들을 한 묶음으로 포장한 게 블록 하나예요. 그리고 모든 블록의 머리에는 바로 앞 블록 전체를 해시한 값이 들어가요.
이 연결이 핵심이에요. 누군가 과거 블록의 내용을 한 글자라도 바꾸면 그 블록의 해시가 달라져요. 그러면 그 해시를 품고 있는 다음 블록과 안 맞고, 다음 블록을 고치면 그다음 블록과 안 맞고, 결국 그 뒤의 모든 블록을 다시 만들어야 해요. 블록이 쌓일수록 과거 조작은 기하급수적으로 어려워져요.
1장의 "장부"가 무엇을 기록하는지였다면, 블록체인이라는 이름은 어떻게 기록하는지예요. 트랜잭션을 블록으로 묶고, 블록을 해시로 사슬(chain)처럼 이어서 과거를 사실상 불변으로 만든 구조.
🔍 블록 안의 거래는 어떻게 한 값으로 묶이나: 머클 트리
블록 하나에는 트랜잭션이 수백 건씩 담겨요. 헤더에 이 목록을 다 넣을 수는 없으니, 값 하나로 요약해서 넣어요.
방법은 단순해요. 거래를 둘씩 짝지어 해시하고, 그 결과를 또 둘씩 짝지어 해시해요. 하나만 남을 때까지 반복하면 나무 모양이 되고, 맨 위에 남은 값이 머클 루트(Merkle Root) 예요.
머클 루트
┌───────┴───────┐
H(AB) H(CD)
┌───┴───┐ ┌───┴───┐
H(A) H(B) H(C) H(D)
│ │ │ │
Tx A Tx B Tx C Tx D거래 하나만 손대도 그 위의 해시가 줄줄이 달라지면서 머클 루트가 바뀌어요. 헤더의 머클 루트만 대조하면 블록 안 거래 전체가 그대로인지 알 수 있어요.
여기서 하나가 더 따라와요. 머클 증명(Merkle Proof) 이에요. "내 거래가 이 블록에 들어 있다"를 확인하려면 블록 전체를 받아봐야 할 것 같지만, 트리 덕분에 몇 개만 받아도 돼요. 위 그림에서 Tx A가 들어 있는지 보려면 H(B)와 H(CD)만 있으면 끝이에요. 내가 가진 Tx A를 해시해 H(A)를 만들고, H(B)와 합쳐 H(AB)를, 다시 H(CD)와 합쳐 나온 값이 머클 루트와 같으면 그 블록에 들어 있는 거예요.
필요한 값이 트리 높이만큼뿐이라, 거래가 1,000건이면 10개, 100만 건이면 20개로 끝나요. 스마트폰 지갑이 수백 GB짜리 장부 없이 "내 거래가 블록에 담겼다"를 확인하는 게 이 원리예요. 크로스체인의 라이트 클라이언트와 PQC의 XMSS 서명도 같은 머클 구조를 씁니다.
합의 프로토콜: 누가 다음 블록을 쓸 자격이 있는가
블록에 트랜잭션을 담을 자격을 어떻게 결정할까요? 아무나 블록을 만들 수 있다면 누가 만든 게 맞는 건지 알 수 없어요.
그래서 블록체인은 이 자격을 얻는 데 큰 비용이 들도록 설계됐어요. 공격해서 얻는 이득보다 공격 비용이 더 크면 아무도 공격하지 않거든요.
PoW (Proof of Work): 비트코인
해시 퍼즐 경쟁이에요. SHA-256 해시를 반복 계산해서 특정 조건을 만족하는 값을 가장 먼저 찾은 노드가 블록을 작성해요. 전기와 하드웨어가 비용이에요.
- 특징: 확률적 Finality, 최장 체인 규칙
- 단점: 에너지 소비가 막대하고, Finality까지 시간이 걸려요
PoS (Proof of Stake): 이더리움
ETH를 예치(Staking)하면 검증자 자격이 생기고, 무작위로 블록 제안자를 선출해요. 부정행위 시 예치금을 몰수(Slashing)해요. 예치금이 비용이에요.
- 특징: 2 Epoch(≈12.8분) 후 결정론적 Finality. 한번 확정된 블록은 사실상 뒤집을 수 없어요. 경제적 보안
- 개선: 에너지 소비를 PoW 대비 99.95% 절감
BFT (Byzantine Fault Tolerance): Hyperledger Besu
허가된 노드(Validator)들만 참여해요. Pre-Prepare → Prepare → Commit 3단계 투표로 2/3 이상 동의해야 블록이 확정돼요.
- 특징: 결정론적 즉시 Finality (1~4초), Fork 불가
- 적합: 금융 인프라, 컨소시엄 블록체인
🛠 시스템을 운영해봤다면: 익숙한 개념과의 대응표
분산 시스템을 다뤄본 개발자라면 이 챕터의 개념이 낯설지 않을 거예요. 비슷한 점과 결정적 차이를 정리하면 이래요.
| 블록체인 | 비슷한 것 | 결정적 차이 |
|---|---|---|
| 노드 | DB 레플리카 (전 노드가 같은 데이터) | 마스터가 없고, 서로를 신뢰하지 않음 |
| Mempool | 메시지 큐의 대기열 | 전 세계가 공유하는 하나의 큐, 수수료 순 처리 |
| 합의 (PoW/PoS/BFT) | Raft·Paxos 같은 분산 합의 | 참여자가 악의적일 수 있다는 가정(비잔틴 장애)에서 출발 |
| Block Finality | DB 트랜잭션의 커밋 | 한번 확정되면 롤백이라는 개념 자체가 없음 |
| 스마트 컨트랙트 (4장) | 저장 프로시저(Stored Procedure) | 배포 후 누구도(만든 사람도) 수정할 수 없음 |
요약하면, 블록체인은 "참여자를 믿을 수 없는 환경에서 돌아가는 분산 DB"예요. 기존 분산 시스템이 장애(crash)를 견디도록 설계됐다면, 블록체인은 거짓말(byzantine fault)을 견디도록 설계됐어요.
그런데 새로운 문제가 생겼어요
트랜잭션으로 숫자를 옮기는 건 해결했어요. 그런데 비트코인을 쓰면서 사람들이 깨달았어요. 블록체인이 결국 "장부에 숫자 옮기기"밖에 안 된다는 걸요.
에스크로, 대출, 보험 등 현실의 금융 계약은 거의 다 조건부예요. "집 등기가 넘어오면 대금을 지급", "담보 가치가 기준 이하로 떨어지면 자동 청산". 이걸 지금은 전부 사람이 중간에서 판단하고 실행해요. 은행, 변호사, 공증인이 그 역할을 하고요.
❓ 다음 챕터의 질문
→ 4장에서 이 문제를 어떻게 해결했는지 알아볼게요.
핵심 요약
| 개념 | 역할 |
|---|---|
| Nonce | 재사용 방지. 복사 공격 차단 |
| Signature | 전체 필드 서명. 바꿔치기 차단 |
| RLP | 표준 직렬화. 파싱 불일치 차단 |
| Mempool | 블록 포함 전 트랜잭션 대기실 |
| 블록 | 트랜잭션 묶음 + 이전 블록 해시. 해시 연결로 과거 불변 |
| Gas | 처리 수수료 + 실행 한도 |
| Block Finality | 블록 확정 = 결제 완결성 |
| PoW | 해시 퍼즐 경쟁. 확률적 Finality |
| PoS | ETH 예치 + Slashing. ≈12.8분 후 결정론적 Finality |
| BFT | 허가 노드 투표. 결정론적 즉시 Finality |
📚 참고자료 보기
- 이더리움 공식 문서: Transactions: 트랜잭션 구조 상세
- 이더리움 공식 문서: Gas and Fees: Gas 메커니즘
- Bitcoin Whitepaper: PoW 합의 원문
- 이더리움 공식 문서: Consensus Mechanisms: PoW/PoS/BFT 비교
- BIS: Payment Finality: 결제 완결성 공식 정의
- Hyperledger Besu: QBFT: BFT 합의 상세