본문으로 건너뛰기
전체 글

총공급량을 정확히 세려면 모든 거래가 같은 기록을 고쳐야 할까

· 약 7분
Bankware Global Engineering

앞 글 수수료가 0이어도 가스는 필요한 이유에서는 실행량을 재는 gas와 실제 청구하는 NIGO를 구분했다. 그 글의 EVM 예제에서는 유료 정책 아래 미리 확보한 최대 수수료가 전부 소각되는 것은 아니었다. 실행 결과를 정산한 뒤 실제 비용만 소각하고 나머지는 돌려준다.

이제 블록 안에 그런 거래가 여러 개 있다고 하자. 각 거래가 서로 다른 잔액을 사용하더라도, 수수료가 소각될 때마다 “NIGO 총공급량”이라는 같은 기록을 직접 고쳐야 한다면 그곳에 공통 쓰기가 생긴다. 총공급량을 정확히 유지하면서, 이 공유 기록을 모든 거래의 입력과 출력에 넣지 않을 수 있을까?

nigo-protocol은 거래마다 실제 잔액 변화와 비용을 검증하고, 블록에 최종 포함되는 결과의 공급 변화를 모아 공급량 기록을 갱신한다. 그 두 단계를 작은 예제로 따라가 보자.

세 거래가 끝나면 1,000은 얼마가 될까​

부모 블록, 즉 이번 블록 직전의 확정 상태에 총 1,000 NIGO가 있다고 하자. A·B·C가 각각 100을, 그 밖의 소유자가 합쳐서 700을 보유한다. D와 E의 잔액은 0이다.

이번 블록에는 EVM을 이용하는 거래 T1·T2·T3가 들어간다. 모두 유효한 거래로 포함되지만, 업무 실행 결과는 다르다. 각 거래는 자신의 보유량에서 수수료용으로 5 NIGO를 미리 확보한다. 아래 금액은 원리를 설명하기 위한 가정이며 실제 수수료나 gas 측정값이 아니다. 추가 발행과 수수료 이외의 소각도 없다.

ID업무 결과수수료 / 반환
T1A가 D에게 10 전송, 성공2 / 3
T2B가 E에게 20 전송을 시도한 뒤 바깥 호출 전체가 REVERT3 / 2
T3C가 금액 이동 없는 업무 실행, 성공1 / 4

표의 금액 단위는 모두 NIGO다. T2는 단순 송금만 한 경우가 아니다. 프로그램이 E로 금액을 이동시켰다가 후속 조건에서 실패해 호출 전체를 되돌린 경우다. E에게 가려던 20은 취소되지만, 이 결과를 내기까지 사용한 실행 비용 3은 남는다. 실패한 거래의 잔존 효과에서 살펴본 업무 변경과 바깥 거래 정산의 차이다.

최종 잔액은 다음과 같다.

  • A는 100에서 D에게 보낸 10과 수수료 2를 뺀 88, D는 10이다.
  • B는 전송이 취소되고 수수료 3만 빠져 97, E는 0이다.
  • C는 수수료 1만 빠져 99, 나머지 소유자의 합계는 700이다.

이를 모두 더하면 994 NIGO다. 미리 확보한 수수료는 합쳐서 15지만, 실제 소각은 6이고 돌아온 금액은 9다. 확보와 반환은 NIGO를 새로 만들거나 없애는 일이 아니다. 공급량을 줄이는 것은 최종 정산에서 소각한 2·3·1이다.

잔액은 거래에서, 공급 합계는 블록에서 검증한다​

각 거래는 자신의 실행 상태에서 잔액과 수수료를 처리한다. T1이 성공했을 때의 A·D 잔액, T2가 되돌린 B·E 잔액도 그 시점에 반영한다. 이후 호출이 잔액을 읽거나 같은 상태에 의존하는 거래가 이어질 때도 그 실행 규칙을 유지해야 한다.

거래별 회계 검증은 실제로 소비한 NIGO 입력과 생성한 출력을 비교한다. 둘 사이의 차이는 검증된 소각 사유로 정확히 설명돼야 한다. 읽기만 한 Cell을 소비한 금액처럼 빼거나, 수수료로 잠시 확보한 금액을 미정산 상태로 남겨 두어서는 안 된다. 서명·권한·자산 정책의 검증도 이보다 앞선 실행 과정의 책임이다. 수량만 맞춘다고 허용된 거래가 되는 것은 아니다.

블록 준비 단계는 이렇게 얻은 결과를 모아 공급 체크포인트를 갱신한다. 이는 NIGO의 현재 총공급량, 최초 발행량, 원인별 누적 소각량을 담은 프로토콜 소유의 상태 Cell이다. 일반 거래가 직접 소비하거나 다시 만들어 쓰는 Cell이 아니다.

예제에서는 부모 공급량 1,000에서 최종 수수료 소각의 합을 뺀다.

1,000−(2+3+1)=9941{,}000 - (2 + 3 + 1) = 994

거래 결과를 순서대로 검증하며 합산하되, 바뀐 공급 체크포인트는 블록 상태에 한 번 반영한다. 모든 거래의 쓰기 목록에 이 공통 Cell을 넣지 않아도, 블록 전체 공급량은 실제 잔액의 합과 맞아야 한다.

현재 NIGO는 genesis의 최초 발행 이후 추가 발행을 허용하지 않는다. 공급 체크포인트는 수수료 소각과, 검증된 EVM 실행에서 살아남은 SELFDESTRUCT에 의한 NIGO 소멸을 구분해 누적한다. 모든 SELFDESTRUCT가 소각을 일으키는 것은 아니며, 되돌려진 실행의 소멸도 세지 않는다. 이 예제에는 후자의 소멸이 없어 수수료 6만 반영했다.

실패 결과는 세고, 폐기한 시도는 세지 않는다​

T2의 업무는 실패했지만 블록에는 그 실패 결과가 유효하게 포함된다. 따라서 수수료 3도 공급 감소에 들어간다. 반면 입력이 잘못돼 제외된 거래나 이번 블록에 들어가지 못한 거래는 이 블록의 소각량에 포함하지 않는다.

같은 원칙은 재실행에도 적용된다. 실행 엔진이 어떤 거래를 시도했다가 그 결과를 폐기하고 다시 실행할 수 있다. 그렇다고 첫 시도와 최종 결과의 비용을 블록 공급량에서 두 번 빼면 안 된다. 집계 대상은 실행 시도의 횟수가 아니라 최종 채택된 거래와 그 실행 결과다.

예를 들어 T3의 폐기된 시도에도 수수료 1이라는 계산이 있었다고 하자. 최종 포함된 T3의 수수료 1만 세므로 공급량은 여전히 994다. 993으로 줄어들지 않는다. 이 설명은 재시도 회계의 원칙이며, 앞의 세 EVM 거래가 실제로 병렬 실행된다는 가정은 아니다.

이를 위해 블록 준비 단계는 포함된 거래 목록과 실행 영수증이 정확히 대응하는지 확인한다. 빠진 결과, 중복 결과, 포함되지 않은 거래의 결과를 합계에 섞을 수 없다. 검증된 소각량 0과 경제 결과가 아예 없는 것도 다르다. 후자를 임의로 0으로 취급하지 않는다.

영수증의 숫자만 믿고 끝내지도 않는다. 각 거래의 수량 보존을 확인한 뒤, 이번 블록에서 실제로 소비·생성한 NIGO의 순감소량과 소각 합계가 일치하는지 다시 대조한다. 블록 중간에 만들어졌다가 뒤 거래가 소비한 Cell은 최종 상태 변화에서 상쇄하므로 중복으로 세지 않는다. 거래가 기록한 이유와 최종 상태의 금액이 함께 맞아야 공급량 994를 받아들인다.

블록이 확정된 뒤 맞춰 보는 통계가 아니다​

공급 집계의 순서는 중요하다. 거래 실행을 마친 뒤 소각 합계와 실제 상태를 대조하고, 공급 체크포인트를 반영한 다음 상태 루트를 계산한다. 상태 루트는 블록이 반영한 상태 전체를 묶어 나타내는 해시다. 체크포인트도 이 해시에 포함되므로 공급량은 대시보드가 사후에 계산한 참고 숫자에 머물지 않는다.

다른 검증 노드도 제안된 블록을 같은 준비 경로로 재실행한다. 거래 결과와 공급량을 포함한 상태가 달라지면 같은 블록 결과를 재현하지 못한 것이다. 거래 처리와 별개로 누군가 “오늘 소각은 6”이라고 보고하는 구조가 아니다.

확정 결과를 저장할 때도 공급 체크포인트를 별도 후처리 작업으로 남기지 않는다. 현재 저장 경로는 상태 변경·거래 영수증·상태 루트 등을 함께 반영한다. 검증한 공급량만 994로 남고, 정작 잔액이나 영수증은 이전 상태로 남는 일을 막기 위한 경계다.

소각량이 0인 블록은 부모 공급량을 그대로 유지한다. 그렇다고 검증을 생략하지는 않는다. 부모 체크포인트와 이번 결과를 확인하고, 경제 값이 같으면 해당 Cell을 다시 쓰지 않는다. 다른 잔액이나 프로그램 상태까지 바뀌지 않는다는 뜻은 아니다.

모든 공급량 검사를 합계로 바꿀 수는 없다​

NIGO도 같은 자산 모델에 포함한 설계는 자산을 표현하고 실행하는 공통 구조를 제공한다. 하지만 모든 자산이 같은 발행 권한과 공급 규칙을 갖는 것은 아니다.

커스텀 자산에 발행 상한이 있다면, 새 발행을 허용할지는 그 거래 시점의 공급 상태를 보고 판단해야 한다. 블록 끝에서 발행량을 더할 수 있다는 이유만으로 이 검사를 생략할 수 없다. 블록 도중의 공급량을 읽어 업무 결과가 달라지는 경우에도, 앞선 거래가 만든 상태를 읽는 의존성이 남는다.

NIGO의 블록 집계가 줄이려는 것은 모든 거래가 총공급량 Cell을 직접 고치도록 할 때 생기는 공통 쓰기다. 같은 잔액이나 정책 상태를 사용하는 거래 사이의 의존성은 여전히 남는다. 실제 병렬 실행 여부는 거래의 읽기·쓰기 관계와 실행 엔진의 규칙을 함께 따른다.

이 글은 2026년 10월 28일 확인한 설계·구현과 회귀 테스트 코드를 바탕으로 한다. 포함된 실패의 수수료, 제외된 결과, 재실행과 최종 상태의 일치, 저장 실패 후 재시도와 정상 재열기 등을 다루는 근거를 확인했다. 이번 집필에서 제품 시험이나 성능 측정을 새로 수행한 것은 아니다.

세 거래 뒤 1,000이 994가 되는 이유는 “수수료를 여섯만큼 썼다”는 보고 하나로 충분하지 않다. 각 거래가 허용된 실행이었는지, 무엇이 실제로 소각됐는지, 어떤 결과가 블록에 남았는지, 그 결과와 최종 상태가 일치하는지를 연결해야 한다. 거래마다 같은 공급량 기록을 고치지 않으면서도, 그 공급량을 원장의 검증 가능한 상태로 유지하는 것이 이 설계의 요점이다.