Nonce in Ethereum
만약 이더리움 네트워크에서 100개의 출금 요청이 동시에 들어오면 어떻게 처리되어야 할까?
이를 잘 처리하기 위해서는 우선 Nonce 에 대해서 알아야한다.
Nonce 는 이더리움 트랜잭션 처리의 핵심 메커니즘으로, 트랜잭션 순서 보장, 이중 지불 방지, Replay Attack 방지의 역할을 모두 수행한다.
Nonce 관리를 잘못하면 트랜잭션이 Pending 상태에 갇히거나, Gap이 발생하여 이후 모든 트랜잭션이 처리되지 않는 상황이 발생한다.
특히 거래소나 DeFi 프로토콜처럼 대량의 트랜잭션을 처리하는 시스템에서 Nonce 관리는 시스템의 안정성과 직결된다.
Nonce 의 정의
Nonce = “Number used ONCE”
Nonce는 “Number used Once”의 약자로, 한 번만 사용되는 숫자를 의미한다.
이더리움에서 Nonce는 각 EOA(Externally Owned Account)에 부여되는 트랜잭션 순차 카운터다.
-
0부터 시작: 새로운 계정의 첫 nonce는 0
-
1씩 증가: 트랜잭션이 체결될 때마다 정확히 1씩 증가
-
순차적: Gap 없이 연속적이어야 함 (0, 1, 2, 3, …)
-
독립적: 각 EOA가 독립적인 nonce 카운터를 가짐
PoW Nonce vs Account Nonce
블록체인을 처음 접하면 “Nonce”라는 용어가 두 곳에서 등장하여 혼란스러울 수 있다.
하지만 이 둘은 완전히 다른 목적으로 사용된다.
PoW(Proof of Work)에서의 Nonce:
-
목적: 블록 해시 퍼즐을 풀기 위한 랜덤 숫자
-
위치: 블록 헤더의 필드
-
범위: 0부터 2^32-1까지 (4,294,967,295)
-
사용 방식: 채굴자가 무작위로 바꿔가며 시도
-
역할:
Hash(BlockHeader) < Target조건을 만족하는 값 찾기
Account Nonce:
-
목적: 트랜잭션 순서 보장 및 Replay Attack 방지
-
위치: 각 계정의 상태 (Account State)
-
범위: 0부터 2^64-1까지 (사실상 무제한)
-
사용 방식: 순차적으로 증가 (0, 1, 2, 3, …)
-
역할: 트랜잭션이 정확히 한 번만 실행되도록 보장
이더리움은 2022년 9월 The Merge 이후 PoS로 전환되었으므로, 현재는 PoW Nonce가 사용되지 않는다. “Nonce”라고 하면 보통 Account Nonce를 의미한다.
Contract Account도 Nonce가 있다
많은 사람들이 Nonce는 EOA(외부 소유 계정)만 가진다고 생각하지만, Contract Account도 Nonce를 가진다.
Contract Nonce의 용도:
-
컨트랙트 생성 시 증가:
CREATEopcode로 새 컨트랙트를 배포할 때마다 +1 -
배포된 컨트랙트 주소 결정: 생성자 주소와 nonce로 새 컨트랙트 주소 계산
컨트랙트 주소 계산 공식:
// CREATE (전통적 방식)
newContractAddress = keccak256(
RLP.encode([deployerAddress, nonce])
)[12:] // 마지막 20 bytes
// CREATE2 (EIP-1014, Uniswap 등에서 사용)
newContractAddress = keccak256(
0xff ++ deployerAddress ++ salt ++ keccak256(initCode)
)[12:]
이것이 Uniswap V2 같은 Factory 패턴이 작동하는 원리다. Factory 컨트랙트가 createPair()를 호출할 때마다 nonce가 증가하며, 각 Pair 컨트랙트의 주소가 결정론적으로 계산된다.
EOA와 Contract Nonce의 차이:
| 특징 | EOA Nonce | Contract Nonce |
|---|---|---|
| 초기값 | 0 | 1 (배포 시 설정) |
| 증가 조건 | 트랜잭션 전송 시 | CREATE 실행 시 |
| 사용 목적 | Replay 방지, 순서 보장 | 컨트랙트 주소 결정 |
| 접근 방법 | eth_getTransactionCount | 상태 트리에서 직접 조회 |
Nonce 의 용도: 왜 필요한가?
Nonce는 단순한 카운터가 아니다. 블록체인의 보안과 무결성을 보장하는 핵심 메커니즘이다.
Replay Attack 이란 공격자가 과거에 전송된 유효한 트랜잭션을 복사하여 다시 전송하는 공격이다.
Nonce 덕분에 한 번 사용된 트랜잭션은 절대 재사용될 수 없다.
뿐만 아니라 이중 지불 역시 방지한다.
이중 지불은 같은 자금을 두 곳에 동시에 보내는 것을 말한다.
Nonce 는 같은 nonce 를 가진 트랜잭션은 하나만 체결되도록 보장한다.
Nonce 는 순서를 보장해주지만, 반대로 교체할 수 있는 메커니즘도 제공한다.
기본적으로 Nonce 의 가장 중요한 역할은 트랜잭션의 순차적 실행을 보장함으로써 순서를 보장하고, 뒤따르는 효과로 Replay Attack 이나 이중 지불을 방지하는 것이다.
다만 트랜잭션 전송 시 Gas 를 너무 낮게 설정한 경우, 같은 Nonce 로 더 높은 Gas 를 제출하면 기존 트랜잭션을 교체할 수 있게 된다.
같은 Nonce 를 가진 트랜잭션은 하나만 체결되기 때문이다.
트랜잭션이 Stuck 된 경우 or 트랜잭션을 취소하고 싶은 경우, 자기 자신에게 0 ETH 를 보내는 트랜잭션에 높은 가스비를 줘서 교체함으로 해결할 수 있다.
Nonce 의 관리
출금용 Hot Wallet 이 있다고 가정하고, 해당 계정의 Nonce 를 어떻게 관리할지에 대해서 생각해보자.
예시를 위해 출금요청이 평균적으로 동시에 100개 들어온다고 했을때, 이를 병렬적으로 처리하게 되면 겹치는 Nonce 가 생길 수 있다.
우선, 이더리움 RPC는 두 가지 방식으로 Nonce 를 조회할 수 있다.
eth_getTransactionCount(“latest”)
-
마지막으로 블록에 포함된 트랜잭션 기준
-
Mompool 의 Pending 트랜잭션은 무시
-
가장 보수적인 값
eth_getTransactionCount(“pending”)
-
Mempool 의 Pending 트랜잭션까지 포함
-
다음에 사용해야할 nonce 반환
-
가장 공격적인 값
당연히 개인적으로 트랜잭션을 단건으로 발생시킬 때에는 위의 두 방법을 써도 되지만, 거래소처럼 수백 수천 건의 동시 출금을 처리해야 하는 경우에는 별도 상태 머신에서 관리하는 것이 일반적이다.
Gap 문제 해결
Gap 은 사실상 Nonce 관리의 최대 적이다.
위에서 중앙 상태머신으로 관리한다고 했는데, Gap 은 왜 생기나? 라고 생각할 수 있다.
경험상 아래 세 가지 경우가 Gap 을 발생시키는 주된 원인이다.
Gap 발생 원인:
-
트랜잭션 실패: nonce N의 트랜잭션이 revert되었지만 재전송하지 않음
-
네트워크 장애: 전송 중 네트워크 끊김, 일부 nonce 누락
-
수동 개입: 관리자가 수동으로 트랜잭션 전송, nonce 순서 어긋남