노드 실행 파일을 먼저 고정하고, 나중에 자산 규칙을 만들었다
자산을 보내는 프로그램에 새 조건을 붙이고 싶다고 하자. 계정마다 일정 구간 안에서 쓸 수 있는 양을 제한하고, 다음 구간이 되면 다시 사용할 수 있게 하는 규칙이다. 이 조건을 추가할 때마다 노드의 코드를 수정하고 실행 파일을 다시 배포해야 한다면, 업무 기능의 확장과 원장 엔진의 변경이 계속 묶인다.
Native Program의 변경안 검증에서는 프로그램이 사용 조건을 판단하고 Core가 그 판단을 실제 원장 변경과 대조하는 구조를 살펴봤다. 그 역할 분담이 새로운 업무에도 성립하는지 확인하려면, 노드가 처음부터 알고 있던 예제를 실행하는 것보다 더 분명한 조건이 필요하다.
2026년 9월 27일에는 노드 실행 파일을 먼저 고정하고 실제로 기동한 뒤, 새로운 정책을 작성했다. 이후의 성공과 실패를 모두 같은 실행 파일에서 확인했다. 새 업무를 처리하기 위해 노드에 무엇을 더 넣어야 했는지, 그리고 무엇은 그대로 둘 수 있었는지가 이번 글의 질문이다.
32개 블록마다 다시 쓸 수 있는 예산
새 정책의 이름은 WindowBudget이다. 계정마다 32개 블록으로 나눈 구간 안에서 최대 100까지 예산을 사용하게 한다. 블록 높이는 원장에 쌓인 블록의 순번이다. 이 예제의 한 구간은 높이 96부터 127까지이고, 다음 구간은 128부터 159까지다. 32개 블록이 실제 시간으로 몇 분인지는 고정하지 않는다.
정책에는 예산 사용량만 기록하는 consume와 자산을 다른 사람에게 보내는 transfer가 있다. 두 작업은 같은 계정의 예산을 함께 사용한다. 다만 consume(40)은 사용량 40을 기록할 뿐 자산 40개를 없애거나 다른 사람에게 옮기지는 않는다.
A가 자산 X를 200개 가지고 있고 B는 0개인 상황을 보자. X의 추가 발행과 소각은 없으며, A는 아직 이번 구간의 예산을 사용하지 않았다. 이 자산의 전송은 예산 정책의 검증을 거치도록 한다. 수수료용 NIGO는 별도로 준비하며 아래 X 잔액과 합산하지 않는다.
아래 수량과 높이는 실제 정책의 규칙을 설명하기 위한 예제다. 첫 세 요청은 모두 높이 96~127 안에서 준비하고 실행한다고 가정한다.
| 요청과 결과 | 구간 사용량 | X 잔액 A / B |
|---|---|---|
| 사용량 40 기록: 성공 | 40 | 200 / 0 |
| B에게 50 전송: 성공 | 90 | 150 / 50 |
| 사용량 20 추가: 거절 | 90 | 150 / 50 |
| 다음 구간에서 20 기록: 성공 | 20 | 150 / 50 |
세 번째 요청은 자산이 부족해서 실패한 것이 아니다. A에게는 여전히 150 X가 있지만, 이번 구간의 사용량 90에 20을 더하면 예산 한도 100을 넘는다. 정책은 이 변경을 거절하고 이전 사용량과 X 잔액을 유지한다. 네 단계 모두 X의 총공급은 200이다. 여기서 유지되는 것은 업무 상태이며, 실패한 서명 거래의 수수료나 처리 기록까지 모두 사라진다는 뜻은 아니다.
다음 구간이 시작됐다고 모든 계정의 기록을 즉시 지우는 작업을 돌리지는 않는다. 새 구간에서 처음 성공하는 쓰기가 그 구간의 사용량을 계산해 저장한다. 이전 기록을 조회하는 동작만으로 상태가 새로 만들어지거나 초기화되지는 않는다.
요청을 미리 준비한 시점과 실제 실행되는 블록이 다른 구간에 속할 수도 있다. 따라서 마지막 행은 새 구간에 도달한 뒤 다시 준비한 요청이다. 오래된 변경안을 제출해도 자동으로 새 구간에 맞춰 성공한다는 약속은 아니다.
노드가 먼저 정책을 몰랐어야 한다
이 예제가 성공했다는 사실만으로 업무 확장을 입증할 수 있을까? 만약 노드 안에 WindowBudget을 처리하는 전용 코드가 이미 들어 있었다면, 정책 이름과 설정을 바꿔 실행한 것일 수도 있다.
그래서 실험 순서를 고정했다. 먼저 검증할 소스에서 노드 실행 파일인 JAR를 만들고, 그 파일의 내용을 식별하는 해시를 기록했다. 실제 노드 프로세스를 기동해 요청을 받을 준비가 된 것을 확인한 뒤에야 새 정책을 작성했다.
정책은 Solidity로 작성했다. 계정별 사용량을 담는 자료 형식과 호출 방법도 함께 정하고, 프로그램을 컴파일해 공개 설치 경로로 공급했다. 자산을 만드는 과정에서는 기존 등록 프로그램의 생성 승인 절차를 거쳤다.
이 과정에서 노드를 다시 빌드하지 않았다. 새 업무를 해석하는 Java 코드를 추가하거나, 시작 상태에 이 정책만을 위한 데이터를 미리 넣지도 않았다. 매번 노드를 시작할 때 고정해 둔 실행 파일의 해시를 확인해 같은 파일을 사용했는지도 대조했다.
따라서 이 실험에서 “새 정책”이라는 말은 새 이름을 붙였다는 뜻보다 강하다. 엔진이 준비된 뒤에 추가한 업무 코드와 상태 형식이, 기존 설치·실행·검증 경로를 통해 동작했다는 의미다.
업무 규칙을 몰라도 원장 검증은 남는다
노드가 WindowBudget의 업무 의미를 미리 알지 못한다면, 잘못된 변경까지 프로그램이 마음대로 허용하는 것은 아닐까?
여기서 업무 규칙과 원장의 공통 규칙을 나눌 필요가 있다. 32개 블록을 한 구간으로 나누는 방법, 한도 100, 두 함수가 사용량을 공유하는 조건은 정책이 해석한다. 사용량이 90인 상태에서 20을 더할 수 없는 이유도 정책 안에 있다.
Core는 프로그램에 전달하는 현재 상태와 실행 맥락을 원장에서 가져온다. 거래의 서명과 입력의 유효성, 변경할 상태에 대한 권한, 프로그램이 허용한 효과와 실제 입출력의 일치를 공통 규칙으로 검사한다. 프로그램이 자신의 업무 조건을 통과시켰다는 이유만으로 다른 자산의 입력이나 허용되지 않은 상태를 바꿀 수는 없다.
이번 정책의 설정과 사용량은 공통 상태 형식인 Cell 안의 바이트 자료로 저장했다. Solidity 프로그램이 그 바이트를 구간·사용량 등의 업무 값으로 읽는다. 노드에 업무별 자료 해석기를 추가하지 않아도, Core는 그 기록의 소유·변경 권한과 전이 규칙을 검사할 수 있었다.
예제의 50 X 전송에는 두 결과가 함께 필요하다. 자산 잔액이 200/0에서 150/50으로 바뀌어야 하고, A의 구간 사용량은 40에서 90으로 늘어나야 한다. 정책은 두 변경의 관계를 검사하고, Core는 허용된 결과가 실제 원장 변경과 맞는지 확인한다. 이 역할 분담이 있어야 새로운 업무를 추가하면서도 공통 원장 규칙을 유지할 수 있다.
성공보다 거절과 재시작이 더 많은 것을 알려줬다
정책을 설치하고 한 번 호출하는 것만으로는 사용량을 제대로 관리하는지 알기 어렵다. 이전 사용량을 무시하는 구현도 첫 요청에는 성공할 수 있기 때문이다.
검증에서는 허용되는 요청과 한도를 넘는 요청을 함께 보냈다. 한도 초과 요청도 실제로 서명해 제출하고 실패 결과를 확인했다. 이후 업무 상태를 다시 읽어 거절한 요청의 사용량이 남지 않는지 검사했다. 다음 구간에서는 요청을 새로 준비해 다시 사용할 수 있는지도 확인했다.
Native 요청과 ethers·viem을 통한 EVM 호출도 같은 정책의 상태를 확인했다. 예제에서 consume와 전송이 공유하는 예산은 진입점마다 따로 주어지는 한도가 아니다. 어느 요청 경로를 쓰더라도 같은 계정의 업무 상태를 기준으로 규칙이 적용돼야 한다. 실행 영역 사이의 세부 연결 구조보다, 여기서는 추가한 규칙을 기존 호출 경로에서도 일관되게 사용할 수 있는가를 확인했다.
저장소는 H2와 RocksDB, 금전 수수료 조건은 ZERO와 FIXED로 나눠 네 조합에서 실행했다. 각 조합에서 최초 실행 뒤 노드를 정상 종료하고 같은 데이터베이스를 다시 열었다. 저장된 상태를 읽고 후속 거래를 처리한 다음, 두 번째 정상 재시작에서도 결과를 대조했다.
이 검증은 새 정책의 업무 상태가 한 프로세스의 기억에만 머물지 않는지 확인한다. 정상 재시작 결과를 컴퓨터 전원 차단의 내구성까지 확대한 것은 아니다. 테스트용으로 추가한 이 정책이 모든 운영 환경에 검증된 표준 상품이라는 뜻도 아니다.
확장할 수 있다는 것 다음에 남는 질문
이 실험이 보여준 것은 구체적이다. 고정한 엔진에 새로운 업무 코드를 설치하고, 그 규칙에 따른 허용과 거절을 처리하며, 저장된 업무 상태를 정상 재시작 뒤에도 이어 쓸 수 있었다.
새 업무를 추가할 수 있는지와 큰 입력을 충분한 비용 안에서 처리할 수 있는지는 별도의 질문이다. 같은 시기의 비용 검사에서는 입력이 많은 거래의 변경안 준비가 실행량 한도에 걸렸고, 후속 개선이 필요했다. 기능 확장의 성공을 성능 보장으로 읽지 않도록 두 결과를 구분했다. 여기서 설명한 실증은 9월 27일에 고정한 엔진과 그 뒤 작성한 정책의 범위다.
이 확장 구조를 자산 모델과 함께 보면 다음 질문으로 이어진다. 기본 코인 NIGO와 다른 자산에 공통 규칙을 어떻게 적용할 수 있을까? 별도 정책 검증이 필요 없는 소유자 전송에는 어떤 경로가 적절할까?
NIGO도 하나의 자산으로 다루는 통합 모델은 이 질문을 이어받는다. 새 규칙을 추가할 수 있는 프로그램 구조를 바탕으로, 단순한 소유자 전송과 정책 검증을 거치는 전송을 한 원장 안에 어떻게 함께 둘지 살펴본다.