이벤트 로그로 풀 상태 추적하기: 안전한 파싱과 Sync·재구성 처리
TECH INSIGHT / EXPLAINED
로그 바이트를 제대로 풀었다고 풀 상태까지 정확한 것은 아닙니다. 먼저 컨트랙트와 ABI를 확인하고, 이벤트가 거래량인지 예비금 전체 값인지 구분해야 합니다.
바이트를 읽기 전에 이벤트 확인
익명 이벤트가 아니라면 topics[0]은 이벤트 시그니처의 Keccak-256 해시입니다. indexed 주소는 topic에, 나머지 인자는 ABI 인코딩된 data에 들어갑니다. 동적 indexed 값은 원문 문자열이나 배열이 아니라 해시일 수 있습니다.
ERC-20 Transfer를 풀 때는 발행 컨트랙트, 시그니처, topic 세 개, data 32바이트를 먼저 검사합니다. 잘린 로그를 읽다가 패닉이 나거나 다른 ABI를 조용히 잘못 읽는 일을 막기 위한 경계입니다.
Swap만 모으면 예비금이 틀릴 수 있다
Uniswap V2 Swap의 네 값은 교환량입니다. 유동성 추가·제거와 명시적 sync도 예비금을 바꾸므로 Swap 변화량만 누적하면 빠지는 경우가 생깁니다. 특정 블록에서 읽은 초기 상태에 Sync(uint112,uint112)의 예비금 값을 적용하세요. 이는 V2 pair 구현의 규칙이며 다른 프로토콜에는 그대로 적용할 수 없습니다.
def decode_sync(data: bytes):
if len(data) != 64:
raise ValueError('Sync requires two ABI words')
values = (int.from_bytes(data[:32], 'big'),
int.from_bytes(data[32:], 'big'))
if any(v >= 2**112 for v in values):
raise ValueError('not canonical uint112 values')
return values
payload = (100).to_bytes(32, 'big') + (200).to_bytes(32, 'big')
assert decode_sync(payload) == (100, 200)스냅숏과 구독 사이의 빈틈 막기
기준 블록의 getReserves와 블록 해시를 함께 저장합니다. 이후 로그를 조회해 블록 번호·트랜잭션 인덱스·로그 인덱스 순서로 적용하고, 블록 해시·트랜잭션 해시·로그 인덱스로 중복을 걸러냅니다. 과거 구간을 따라잡는 동안 실시간 이벤트를 버퍼링해야 인수인계 구간이 비지 않습니다.
WebSocket 알림은 영구 큐가 아닙니다. 재접속하면 저장된 체크포인트 이후 로그를 다시 조회합니다. removed 로그나 체크포인트 해시 변경을 발견하면 영향받은 상태를 되돌리거나 신뢰할 수 있는 조상 블록에서 재구축합니다. 잠정 상태와 확인된 상태도 구분합니다.
작은 파서와 큰 운영 경계
예제는 Sync 두 ABI word의 길이와 uint112 범위만 확인합니다. 구독·컨트랙트 선택·reorg 처리는 포함하지 않으므로 별도로 검증해야 합니다. 토큰 정수를 사람이 읽는 수량으로 표시할 때 decimals가 필요하지만 예비금 정수 자체가 바뀌는 것은 아닙니다.
주소를 자르기 전에 왼쪽 패딩을 검사한다
indexed address는 32바이트 topic에서 오른쪽 20바이트에 주소가 들어가고 왼쪽 12바이트는 0이어야 합니다. 마지막 20바이트만 잘라 H160으로 변환하면 잘못된 상위 바이트가 있어도 정상 주소처럼 보입니다. Solidity ABI의 주소 인코딩에 맞춰 길이와 패딩을 먼저 검증하세요.
def indexed_address(word):
if len(word) != 32 or any(word[:12]):
raise ValueError("noncanonical address topic")
return "0x" + word[12:].hex()
독립 Python 실험에서는 정상 32바이트 값을 통과시키고 31바이트·33바이트·상위 12바이트가 0이 아닌 값을 거절했습니다. 이 함수는 이미 hex에서 변환된 bytes를 받습니다. JSON 단계의 0x 접두사·짝수 길이·잘못된 hex 문자 검사는 호출 전에 따로 필요하며, 문자열 길이를 바이트 길이처럼 비교하면 안 됩니다.
시그니처가 같아도 indexed 배치가 다를 수 있다
ERC-20과 ERC-721의 Transfer는 모두 Transfer(address,address,uint256) 시그니처를 사용합니다. 그러나 일반 ERC-20은 수량이 data에 있고 ERC-721 명세에서는 tokenId가 indexed이므로 topic에 있습니다. 시그니처 해시는 indexed 여부를 담지 않기 때문에 topic0만으로 둘을 구분할 수 없습니다.
이 차이가 발행 주소별 ABI와 topic 개수·data 길이를 함께 검사하는 구체적인 이유입니다. 구조가 어긋난 이벤트를 다른 파서로 자동 재해석하지 말고 예상 ABI 불일치로 격리하세요. 같은 엄격함을 Sync에도 적용해 uint112 최대값은 허용하고 2^112는 거절해야 상위 패딩이 섞인 값을 준비금으로 받아들이지 않습니다.
removed를 중복 필터보다 먼저 처리한다
이미 처리한 로그와 같은 키로 removed=true 알림이 왔다고 '본 적 있는 로그'로 버리면 되돌림을 놓칩니다. 먼저 정식 체인 상태 변경 알림으로 분류하고, 저장된 로그의 적용 상태를 변경해야 합니다. 정상 로그의 재전송과 제거 알림은 의미가 다른 입력입니다. Geth 구독 문서도 재구성 시 제거 로그와 새 체인의 로그가 재전송될 수 있음을 설명합니다.
여러 체인을 한 저장소에 모으면 키에 체인 ID도 추가합니다. 블록 해시·트랜잭션 해시·로그 인덱스는 분기 위의 한 로그를 식별하지만 블록 높이만으로는 재구성 전후 로그를 구분할 수 없습니다. 제거 알림을 놓쳤거나 순서가 뒤섞였을 때는 알림 도착 순서로 현재 상태를 결정하지 말고 헤더의 정식 체인 관계를 다시 확인합니다.
로그가 없는 블록도 원장에 남긴다
가상 기준 블록 100의 준비금이 (100, 200), 옛 블록 101a 적용 후 (110, 190)이라고 합시다. 새 분기의 101b가 (95, 210)을 기록하면 공통 조상 100까지 되돌리고 101b를 적용해야 합니다. 옛 Sync 값을 새 값에서 단순히 빼는 방식은 잘못입니다. Sync는 변화량이 아니라 대체할 전체 준비금이기 때문입니다.
블록 원장에 번호·해시·부모 해시·적용 전후 상태를 남기고 새 102b에 관련 로그가 없어도 체크포인트를 전진시킵니다. 상태와 체크포인트를 한 번에 저장하면 중간 장애로 로그만 적용하고 위치는 옛 값인 상태를 줄일 수 있습니다. 독립 가상 재생에서는 중복을 한 번만 적용하고 101a를 되돌린 뒤 (95, 210)을 복원했습니다. 이는 운영 구독이나 실제 체인 재구성을 시험한 결과는 아닙니다.
공식 자료와 함께 읽기
Solidity ABI events · Uniswap V2 pair source · Geth subscriptions
내용·예제 검토: 2026-10-01
댓글
댓글 쓰기