StateCell의 소유자: 누가 상태를 바꿀 수 있을까?
A가 S에게 자신의 자산을 30까지 대신 사용할 수 있도록 허용했다. S는 그중 20을 B에게 보내려 한다. 이번 거래에 서명하는 사람은 S다. 하지만 사용할 자산은 A의 것이다.
원장이 ‘자산의 소유자가 직접 서명했는가’만 검사한다면 이 거래는 처리할 수 없다. 그렇다고 S가 A의 자산을 사용하겠다고 적은 것만으로 허용할 수도 없다. A가 무엇을 승인했는지, S의 요청이 그 범위 안에 있는지 함께 판단해야 한다.
누구의 상태인가와, 누가 어떤 조건으로 그 상태를 바꿀 수 있는가. StateCell에 소유자라는 개념을 넣으면서 함께 풀어야 했던 질문이다.
StateCell은 원장의 상태를 담는 작은 기록 단위다. 1편에서는 자산과 스마트컨트랙트의 상태를 이 공통 단위로 표현하는 이유를, 2편에서는 값의 구조를 표현하는 방법을 살펴봤다. 이번에는 위의 사용 승인 예제를 따라 소유 정보와 변경 권한, 프로그램과 원장의 역할을 설명한다.
내 자산을 다른 사람이 사용할 수 있다면
먼저 원장이 자산의 종류와 보유자, 수량을 직접 관리하고 소유자의 서명으로 이전을 허용하는 경우를 생각해보자. A는 이 자산을 100 가지고 있고, B는 아직 가지고 있지 않다. 수수료와 추가 발행·소각은 예제에서 제외한다.
A가 직접 보내는 경우는 이해하기 쉽다. 원장은 자산 기록에서 소유자가 A임을 확인하고,
서명을 통해 A의 요청인지 검사한다. StateCell에서는 이 소유 정보를 owner에 기록한다.
owner는 해당 셀의 소유 주체를 나타내는 주소다.
다른 사람에게 사용을 허용하려면 기록이 하나 더 필요하다. A가 서명해 S에게 30까지 사용할 수 있도록 승인하면, 자산 기록 옆에 그 승인을 별도의 상태로 남긴다.
자산 기록
소유자: A
수량: 100
사용 승인 기록
승인자: A
사용자: S
한도: 30
두 기록은 같은 자산에 관한 것이다. 사용 한도는 별도로 예치한 자산이 아니므로, 승인했다고 A의 100이 줄거나 S에게 30이 생기지는 않는다. S가 받은 것은 A의 자산을 정해진 범위에서 사용할 권한이다.
이제 S가 그중 20을 B에게 보내면 결과는 다음과 같아야 한다.
| 기록 | 사용 전 | 사용 후 |
|---|---|---|
| A의 자산 | 100 | 80 |
| B의 자산 | 0 | 20 |
| S의 남은 사용 한도 | 30 | 10 |
자산의 합계는 계속 100이다. S는 A의 자산을 자기 소유로 넘겨받는 과정을 거치지 않고, 승인받은 권한으로 20을 B에게 이전한다.
여기서 owner의 역할이 분명해진다. 원장은 요청의 대상이 A의 자산임을 확인하면서도,
조건을 갖춘 S가 이전을 요청할 수 있게 해야 한다. 소유 정보는 사용 권한을 판단할 출발점이며,
승인 조건을 함께 보아야 이번 변경이 가능한지 알 수 있다.
사용 조건은 프로그램에, 공통 규칙은 원장에
이 거래를 허용하려면 누군가는 승인 기록을 읽고 질문에 답해야 한다. A가 S에게 승인한 기록이 맞는가. S가 보내려는 20은 한도 30 이내인가. 사용 후 한도는 정확히 10으로 줄어드는가.
이런 조건은 자산이나 서비스에 따라 달라질 수 있다. 사용 기간을 추가하거나 승인 방식을 바꿀 때마다 원장 자체를 수정해야 한다면 새로운 규칙을 도입하기 어려워진다. 그래서 개별 사용 조건을 검사하는 일은 프로그램이 맡도록 한다.
앞의 예제에서 거래는 사용할 자산과 승인 기록, 그리고 변경 후 남겨야 할 기록들을 제시한다. 프로그램은 이 변경안을 받아 승인 조건에 맞는지 검사한다. 허용되는 변경의 규칙을 코드로 표현하고, 원장은 그 검증 결과를 받아 공통 규칙과 함께 판단하는 구조다.
그렇다면 프로그램이 허용했다고 하면 원장은 모두 받아들여도 될까. 승인 한도를 지켰더라도 거래가 없는 자산을 만들어내거나, 이미 사용한 자산 기록을 다시 사용해서는 안 된다. 승인 조건을 검사하는 프로그램이 다른 프로그램의 상태까지 바꾸도록 허용해서도 안 된다.
두 역할을 나누면 다음과 같다.
- 프로그램은 A가 S에게 허용한 한도와 이번 사용 조건, 사용 후 승인 기록이 어떻게 바뀌어야 하는지 검사한다.
- 원장은 해당 자산에 허용된 프로그램의 검증을 거쳤는지, 그 검증이 실제로 사용할 기록을 대상으로 하는지 확인한다. 기록의 유효성과 자산 수량의 보존, 허용된 상태만 변경하는 규칙도 검사한다.
니고에서는 이렇게 거래가 제시한 변경안을 검증하는 프로그램을 Native Program이라고 부른다. 원장의 공통 규칙을 집행하는 부분은 **프로토콜 코어(Core)**다.
Native Program의 설계와 동작 방식은 별도 포스트에서 자세히 다룰 예정이다.
이 구분은 승인 기록의 소유자에도 적용된다. 니고의 기본 자산 승인 기록은 승인한 사람 A를
owner로 가진다. 동시에 원장은 그 기록을 어떤 프로그램의 규칙으로 변경해야 하는지도 구분한다.
상태를 식별하는 ID에 소유 정보와 별개로 그 관리 영역과 프로그램을 구분할 정보가 들어간다.
따라서 A가 소유한 자산 기록과 A가 소유한 승인 기록은 같은 방식으로 변경할 필요가 없다. 자산을 직접 보내는 요청에는 소유자의 서명과 원장 규칙을 적용하고, 승인 기록을 바꾸는 요청에는 그 기록을 관리하는 프로그램의 검증을 더한다. A의 서명이 있다는 사실만으로 프로그램의 규칙을 건너뛰고 승인 기록을 임의의 값으로 바꿀 수는 없다.
이렇게 하면 상태의 소유자를 유지하면서 변경 규칙을 프로그램으로 확장할 수 있다. 원장은 모든 서비스의 승인 방식을 일일이 알아야 할 필요가 줄고, 프로그램은 원장이 제공하는 공통 규칙 위에서 자신의 조건에 집중할 수 있다.
스마트컨트랙트에 기록한 내 잔액은 누구의 상태일까
같은 승인과 이전을 토큰 스마트컨트랙트로 표현하는 경우도 생각해보자. 앞의 거래에 이어서 20을 더 보내는 것이 아니라, A의 100에서 B에게 20을 보내는 동일한 상황을 다른 방식으로 처리하는 예다. 이 컨트랙트를 T라고 하겠다.
T는 자신의 저장소에 A의 잔액과 S에게 허용한 한도를 기록한다. S가 이전 함수를 호출하면 T의 코드가 잔액과 한도를 읽고 조건을 검사한 뒤, A의 잔액을 80, B의 잔액을 20, 남은 한도를 10으로 바꾼다.
여기에는 두 관점이 겹쳐 있다. 토큰을 사용하는 사람에게는 A의 잔액이다. 하지만 원장이 저장하고 실행하는 관점에서는 T가 관리하는 데이터다. A가 자신의 잔액이라고 해서 그 저장값을 1000으로 직접 고칠 수는 없다. 잔액의 의미와 변경 조건을 정하는 T의 코드를 거쳐야 한다.
스마트컨트랙트를 실행하는 가상 머신인 EVM에서는 이런 데이터를 저장하는 위치를 슬롯이라고
부른다. 니고가 이 슬롯을 StateCell로 표현할 때, 셀의 owner에는 저장소가 속한 컨트랙트
T의 주소가 들어간다.
| 원장에 저장하는 기록 | 셀의 owner |
|---|---|
| 원장이 직접 자산으로 다루는 A의 자산 기록 | A |
| T의 저장소에서 A의 잔액을 담는 슬롯 | T |
두 번째 행에서도 토큰의 보유자는 A다. 다만 A와 잔액의 관계를 T의 데이터와 코드가 표현한다.
슬롯의 owner를 T로 둔다는 것은 그 상태가 어느 컨트랙트의 실행 규칙 아래 있는지 나타내는 것이다.
배포한 사람이나 컨트랙트 관리자의 개인 주소를 뜻하지 않는다.
앞에서 본 승인 검증 프로그램과 이 스마트컨트랙트는 상태 변경을 다루는 방식도 다르다. 승인 검증 프로그램은 거래가 제시한 변경 전후가 조건에 맞는지 검사한다. 일반 EVM 컨트랙트는 함수를 실행하면서 상태를 읽고 변경 결과를 계산한다. Native Program도 Solidity로 작성할 수 있지만, 이처럼 실행에서 맡는 역할은 다르다.
원장이 이해하는 데이터의 의미에도 경계가 있다. 원장이 직접 다루는 자산은 어떤 자산의 수량인지 알고 보존 여부를 검사한다. T의 저장소에 있는 숫자가 잔액인지 사용 한도인지는 T의 코드가 정한다. 원장이 모든 컨트랙트의 숫자를 자산 잔액으로 해석하는 것은 아니다.
StateCell은 이 상태들을 같은 기록 형식으로 담는다. 그 위에서 사용자 자산은 자산의 이전 규칙을, 컨트랙트 상태는 해당 컨트랙트의 실행 규칙을 따르게 한다. 공통 저장 단위를 사용하면서도 각 상태의 의미와 변경 책임을 맡을 곳에 남겨두는 것이다.
자산 이전과 한도 감소는 함께 일어나야 한다
이제 처음의 거래를 끝까지 반영해보자. A의 자산 100 중 20이 B에게 가고, A에게는 80이 남으며, S의 사용 한도는 30에서 10으로 줄어야 한다.
각 기록을 따로 저장하면 곤란한 중간 결과가 생길 수 있다. B에게 20을 보냈는데 한도가 여전히 30이면 같은 권한을 다시 사용할 수 있다. 반대로 한도만 10으로 줄고 B에게 자산이 가지 않으면 사용하지 못한 권한이 사라진다.
따라서 상태를 나누어 저장하더라도, 하나의 거래에 속한 변경은 함께 반영해야 한다. 이를 원자성이라고 부른다. 이 거래의 자산 이전과 승인 기록 변경이 모두 성공하거나, 그 업무 변경이 모두 폐기되어야 한다는 뜻이다.
니고는 거래를 처리하는 동안 변경분을 임시 작업 공간에 모은다. 자산과 승인 기록의 검증이 끝나면 그 변경 묶음을 반영한다. 업무 실행이 실패하면 묶음을 버리므로, 예제의 자산과 한도는 거래 전의 A=100, B=0, 한도=30으로 남는다. 실행 비용은 이 업무 상태 변경과 별도로 정산한다.
여기서 서로 다른 실행 방식이 다시 만난다. 거래가 변경안을 제시했든 컨트랙트 함수가 변경을 계산했든, 저장 계층에는 소비할 셀과 생성할 셀의 묶음으로 전달할 수 있다. 자산 기록은 이전 기록을 소비하고 새 기록을 만들며, 컨트랙트 슬롯은 같은 위치의 셀을 새 값으로 교체한다. 두 방식 모두 관련된 변경을 하나의 묶음으로 다룬다.
셀은 상태를 표현하는 단위이고, 거래는 함께 성공해야 할 변경을 묶는 단위다. 이 두 단위를 구분하면 작은 상태를 개별적으로 식별하면서도 여러 상태 사이의 관계를 지킬 수 있다.
지금의 상태에 이르는 경로도 남긴다
거래가 끝난 뒤 A의 자산이 80이라는 결과만 보면, 그 값이 어떻게 만들어졌는지는 알 수 없다. 앞의 거래가 어떤 상태를 사용하고 무엇을 남겼는지 함께 보면 변화의 경로가 드러난다.
이전 상태
A의 자산 100, 사용 한도 30
↓
S가 B에게 20을 보내는 거래
↓
이후 상태
A의 자산 80, B의 자산 20
남은 사용 한도 10
새로 생긴 B의 자산 20에서 그것을 만든 거래로, 다시 그 거래가 사용한 A의 자산과 승인 기록으로 거슬러 올라갈 수 있다. B가 받은 자산을 다음 거래에서 사용하면 그 거래도 같은 기록을 통해 연결된다. 이런 **상태와 거래의 연결 관계를 트랜잭션 리니지(transaction lineage)**라고 부른다. 여기서 리니지는 지금의 상태에 이르기까지 생성과 변경이 이어진 경로를 뜻한다.
이 연결은 거래의 실행 결과에 어떤 셀이 소비되고 어떤 셀이 생성되었는지 남기는 데서 출발한다. 거래별 변경 기록을 보존하고 조회하면서 이 연결을 따라간다. 컨트랙트 슬롯처럼 같은 위치의 값을 교체하는 경우에도, 어느 거래가 그 상태를 바꿨는지 이 기록을 통해 확인할 수 있다.
어떤 셀이 어느 거래에서 생성·소비·변경되었는지는 공통 기록으로 확인하고, 그 변화의 업무상 의미는 각 프로그램의 규칙으로 해석한다. 자산의 이동과 승인 한도 변경을 같은 관계로 읽을 수 있는 것이다.
1편에서 소유기간 연구를 원장 설계로 연결한 이유도 여기에 닿아 있다. 상태 변화의 경로를 공통으로 추적할 수 있으면 소유 이력과 권리의 변화를 해석할 기반이 생긴다. 그 이력을 어떤 기간이나 권리로 계산할지는 프로그램이 정한다. 원장은 현재 상태를 담는 일과 함께, 그 상태를 만든 거래들의 관계를 다룰 수 있어야 한다고 보았다.
소유 정보 위에 사용 규칙을 세운다
처음의 질문으로 돌아가면, S가 A의 자산을 사용할 수 있는 이유는 A가 그 사용을 승인했고 거래가 승인 범위를 지키기 때문이다. 자산의 소유 정보, 프로그램이 검사하는 사용 조건, 원장의 공통 검증이 함께 있어야 이 관계를 표현할 수 있다.
StateCell의 설계에서 중요하게 본 것은 이 역할들을 연결하는 일이었다. 누가 소유하는지 기록하고, 어떤 규칙으로 바꿀지 프로그램에 맡기며, 검증을 마친 여러 변경은 함께 반영하고 그 경로를 남긴다. 사용자 자산과 스마트컨트랙트 상태를 공통 형식으로 담으려면, 저장할 값만큼이나 그 값을 바꾸는 책임을 어디에 둘지가 중요하다.
공유 상태에 관한 질문은 남는다. 여러 거래가 동시에 같은 사용 한도를 줄이려 한다면 각 거래를 독립적으로 처리하기 어렵다. 상태를 작은 셀로 나누는 것과 사용 가능한 수량을 독립적으로 배정하는 것은 별도의 설계 문제다. 이 질문은 향후 Para-cell 독립 포스트에서 이어가려 한다.
StateCell 설계 시리즈 3편. 예제의 인물·자산·수량은 소유와 변경 권한의 관계를 설명하기 위한 가정이다.