# 플라즈마 EVM 2.0 : 탈중앙화된 상태를 강제하는 튜링 완전한 사이드 체인 Carl Park(박주형) (Onther Inc.) Aiden Park(박정원) (Onther Inc.) Kevin Jeong(정순형) (Onther Inc.) Translated by Jason Hwang(황재승) (Onther Inc.) :::info [English Version Link](https://hackmd.io/s/HyZ2ms8EX) ::: ### Abstract 이더리움의 EVM(Ethereum Virtual Machine)은 블록체인에서 튜링 완전한 연산을 지원함으로써 스마트 컨트랙트(smart contract)라는 이름으로 잘 알려진 일반 프로그램(general program)을 동작가능하게 하였다. 플라즈마 EVM은 플라즈마 기반의 사이드 체인에서 EVM을 실행할 수 있는 새로운 버전의 플라즈마이며, go-ethereum, py-evm, parity와 같은 현재 이더리움 클라이언트에 기반을 두고있다. 이 페이퍼에서는 자식체인의 검증된 상태만이 root chain에 제출되는 것을 보장하도록 두 체인 사이의 account storage에서 enter, exit하는 방법을 제공하여 상태가 강제되는(state-enforceable) 플라즈마 구조를 제안한다. 이는 두 체인이 완전히 동일한 구조를 갖기 때문에 가능하다. 이를 이용하여 기존 이더리움 체인에 구축된 탈중앙화어플리케이션을 그대로 플라즈마 체인으로 옮겨와 댑(Dapp)의 탈중앙성, 성능, 안정성, 사용성 모두를 개선시킬 수 있다. ### Glossary - NULL_ADDRESS: 0x00 or 0xFF...FF(2^160 - 1)이고 논스와 서명이 v = r = s = 0인 계정, NA로 표기한다. - Transactor: 트랜잭션을 생성하는 계정, tx.origin. - Root chain: 이더리움 블록체인 - Child chain: 플라즈마 블록체인 - RootChain contract: ETH나 ERC20의 enter/exit 작업을 관리하는 root chain에서의 플라즈마 컨트랙트, $R$로 표기한다. - Requestable contract: Root chain과 child chain 양쪽에서 exit/enter request를 처리할 수 있는 컨트랙트이다. 2개의 같은 컨트랙트가 root chain과 child chain에 모두 배포되어야 하고, $R$을 통해 두 컨트랙트의 주소가 매핑된다. - enterRequest: Root chain에서 보유하고 있는 자산을 child chain으로 예치하거나, account storage 변수를 child chain으로 이동시키는 것 처럼 root chain에서 child chain으로 무언가를 이동시키기 위해 필요한 request를 의미한다. - exitRequest: Child chain에 예치되었던 자산이나 account storage를 다시 root chain으로 이전시키기 위해 필요한 request를 의미한다. Root chain에 대한 exit request는 child chain의 account storage의 상태를 즉시 갱신해야 하며 child chain에서의 갱신이 거절되었다면(트랜잭션이 revert 된 경우), child chain을 갱신할 때 실행되었던 연산 결과를 증거로 삼아 `exitChallenge`를 할 수 있다. - requestBlock: Root chain에 의해 상태 변화(state transition)에 대한 적용이 강제되는 블록 - nonRequestBlock: Child chain에서 계정 간에 발생하는 일반적인 트랜잭션을 포함하는 블록 - requestChallenge: `requestBlock`이 정상적인 request를 포함하지 않거나 유효하지 않은 request를 포함하는 경우에 대한 챌린지 - nullAddressChallenge: `nonRequestBlock`이 NULL_ADDRESS가 전송한 트랜잭션을 포함하는 경우에 대한 챌린지 - computationChallenge: `requestBlock`이나 `nonRequestBlock`에서 상태 변화에 대한 연산이 올바르게 이루어지지 않는 경우에 대한 챌린지. - exitChallenge: Invalid한 `exitRequest`가 child chain에서 받아들여지지 않는 경우에 대한 챌린지 (request는 `requestBlock`에 포함되어야 한다). ### Child chain 플라즈마 EVM의 클라이언트는 기존 이더리움 클라이언트에 기반했기 때문에 대부분의 기본 메커니즘은 이더리움과 동일하다. Child chain의 블록, 트랜잭션 그리고 receipt등의 자료구조는 이더리움과 같다. 다만 플라즈마 프로토콜을 적용하기 위해 아래의 정책은 기존의 이더리움과는 다르다. 1. Child chain을 사용하기 원하는 root chain의 사용자 A의 자산(ETH/ERC20)이 RootChain 컨트랙트에 예치되면, A의 enter request는`enterRequest` 대기열에 쌓이고 operator(혹은 NULL_ADDRESS)에 의해 다음 child chain의 블록에 A의 자산을 *발행*하는 트랜잭션이 생성된다. Root chain에서 child chain으로 상태 전환은 이러한 방식으로 이루어진다. 2. 사용자 A가 child chain에서 exit하기를 원한다면, 해당 증거를 포함하여 request를 하면된다. 해당 request는 `exitRequests` 대기열에 쌓이고(queued on) operator(혹은 NULL_ADDRESS)는 반드시 A의 자산을 *소각*하는 트랜잭션을 다음 child chain의 블록에 생성해야 한다. 3. Root chain의 exit request가 챌린지 없이 완료되면, child chain에는 더 이상 어떤 변화도 발생하지 않는다. 그러나 *root chain*의 컨트랙트는 **반드시** root chain에 해당 변경 사항을 적용해야 한다. 4. 다음 블록에 request와 관련된 트랜잭션이 포함되지 않는다면, 이는 챌린지에 대한 근거가 될 수 있다. 그리고 챌린지가 받아들여지면 root chain은 child chain의 상태를 request 트랜잭션이 발생하기 이전 블록으로 revert 시킨다. Operator는 `requestBlock`과 `nonRequestBlock`, 두 가지 유형의 블록을 root chain에 제출해야 한다. RootChain 컨트랙트는 이 두 종류의 블록을 유기적으로 처리하기 위해 상황에 맞게 `WaitingNonRequestBlock`, `WaitingRequestBlock`, `AcceptingRequest`인 상태로 변한다. 위 세 가지 상태와 request들이 어떤 방식으로 처리되는지 알아보도록 하자. 1. Operator는 반드시 새 블록을 제출하기 전에 `prepareToSubmit` 함수를 호출해야 한다. 그러면 root chain 컨트랙트는 `WaitingNonRequestBlock`인 상태가 된다. 그 다음 operator는 `nonRequestBlock`과 `requestBlock`을 모두 제출한다. 새로운 request들은 `WaitingNonRequestBlock`인 상태가`AcceptingRequest` 로 바뀔 때까지 펜딩된다. 2. 먼저, `nonRequestBlock`은 반드시 enter/exit request와 관련 없는 모든 트랜잭션을 담아야 한다. Child chain에서 생성된 트랜잭션이 없다면, `transactionRoot`, `postTransactionalStatesRoot`는 `emptyTrie`를 가르킨다. NULL_ADDRESS로 부터 전송되는 트랜잭션이 포함되거나 (nullAddressChallenge), 상태 전환이 잘못된 방법으로 이루어 지거나(computationChallenge), byzantine 상황으로부터 child chain을 복구해야하는 경우 챌린지 될 수 있다. - Operator가 `nonRequestBlock`을 제출한 경우, root chain은 `WaitingRequestBlock` 상태가 된다. 3. 두 번째 유형의 블록인 `requestBlock`은 request와 관련된 트랜잭션들만 담아야 한다. 그러므로 `requestBlock`은 enter&exit request의 적용, NULL_ADDRESS, 컨트랙트 주소, state-enforcing 트랜잭션만을 포함해야 한다. - `requestBlock`이 request와 관련없는 트랜잭션을 포함하거나 포함되어야 할 request를 제외하면, 챌린지가 발생하게 되고 해당 블록은 챌린지 되기 전 상태로 revert 된다. 이러한 종류의 챌린지를 `requestChallenge`라고 한다. - operator가 `requestBlock`을 제출하면, root chain은 `AcceptingRequest` 상태가 된다. Root chain은 `requestBlock`을 통해 child chain의 상태 변화를 강제한다. Operator가 이러한 상태 변화를 제대로 적용하지 않아 이에 대한 챌린지를 받는다면 현재 블록에서 상태 변화가 발생하기 이전 블록으로 revert 된다.(*requestChallenge*) ![](https://i.imgur.com/IYZYV5v.png) *<center>request 및 챌린지 다이어그램</center>* #### Epoch Epoch는 N($N>=2$)개의 블록들을 갖고 있고, N-1개의 `nonRequestBlock`과 1개의 `requestBlock`으로 이루어져 있다. 위의 예시는 N이 2인 경우에 대한 설명이다. 그러나 epoch 길이를 확장하여 `requestBlock`과 `nonRequestBlock`의 비율을 변경할 수도 있다. ## Block submission Operator는 각 블록마다 `stateRoot`, `transactionsRoot`, `postTransactionalStatesRoot`(또는 `intermediateStatesRoot`)이 세 종류의 머클 루트를 제출한다. Exit request가 받아 들여지기 위해서는 특정 블록의 계정 상태(PETH 밸런스나 account storage의 값)에 대한 머클증명이 필요하다. 상태 변화에 대한 연산이 제대로 이루어지지 않았다면 `computationChallenge`를 통해 올바른 상태를 가지고 있는 블록으로 복구 된다. 이 방식은 TrueBit의 verification game과 유사한 방식이며, pre-stateRoot 및 post-stateRoot를 사용하여 '트랜잭션 별 상태 전환 함수'를 확인하는 방식으로 해결한다. ### [Block Withholding Attack](https://hackmd.io/kXWTlmyhSP2vVSIzsenU8w?both) 사용자가 block withholding attack을 감지하면 자산 및 상태를 보호하기 위해 사용자가 플라즈마 체인의 fork를 생성할 수 있다. 사용자는 플라즈마 체인을 fork하여 자신의 자산과 상태를 안전하게 root chain으로 exit할 수 있다. 플라즈마 체인이 byzantine이 아닌 경우에는 사용자가 fork를 생성하기 위해서는 상당한 시간과 비용이 필요하다. ![](https://i.imgur.com/sxq9LrZ.png) *<center>Root chain 컨트랙트 상태 변환 다이어그램</center>* ### Finality 챌린지 기간이 지난 후에 블록에 대한 챌린지를 할 수 없는 경우에 대비하여 플라즈마 EVM은 플라즈마 XT처럼 블록의 체크포인트를 지정할 수 있다. 이러한 Finality를 바탕으로 하여 RootChain 컨트랙트를 효율적으로 구현할 수 있다. ### Account Storage Enter & Exit 플라즈마 EVM에서는 어떠한 유형의 account storage 변수도 root chain과 child chain을 자유롭게 이동 할 수 있다. Storage 변수를 enter & exit request에 사용 할 수 있는 이유는 storage는 trie 구조로 되어있고, storage 내의 모든 값에 해당 키를 사용하여 접근 할 수 있기 때문이다. Enter & exit은 계정의 **단일 storage 변수**를 한 체인에서 이에 상응하는 컨트랙트의 다른 체인으로 이동하는 것을 의미한다. #### Notation NULL_ADDRESS: $NA$ Operator: $O$ User of plasma: $U$ Challenger: $Ch$ RootChain contract: $a_R$ Root chain *Requestable contract*의 주소: $a_r$ Child chain *Requestable contract*의 주소: $a_c$ *(각 체인에는 단 하나의 컨트랙트 주소만 있다.) Root chain의 storage 변수 주소: $σ[a_r]s[k]$, 이를 간단히 하면 $A^{r}_{s}[k]$와 같이 나타낼 수 있다. child chain의 storage 변수 주소: $σ[a_c]s[k]$, 이를 간단히 하면 $A^{c}_{s}[k]$와 같이 나타낼 수 있다. (단, $k$ 는 $trieKey$이고 $A_s$는 $σ[a]s$ 일 때) Trie의 *임의의 storage 변수*는 $A_s[k]$이고 root chain과 child chain의 request 가능한 컨트랙트 주소를 $a_r$과 $a_c$라 하자. 이것들의 storage 변수는 각기 $A^{r}_{s}[k]$와 $A^{c}_{s}[k]$이다. Storage 변수의 enter & exit request는 storage 변수가 $A^{r}_{s}[k]$와 $A^{c}_{s}[k]$ 사이를 이동하는 것으로 정의한다. Enter는 $A^{r}_{s}[k]$을 바탕으로 $A^{c}_{s}[k]$가 변경되는 과정이고 exit은 $A^{c}_{s}[k]$를 바탕으로 $A^{r}_{s}[k]$가 변경되는 과정이다. 그러나, 상태를 바꾸는 구체적인 방법은 구현 방식에 따라 달라질 수 있다. Storage의 변경을 적용하기 위에서는 $a_r$과 $a_c$가 같은 바이트코드를 갖고 있어야 하며, 두 컨트랙트 주소가 같은 바이트 코드를 갖고 있다는 것은 두 컨트랙트가 같은 storage 레이아웃을 갖고 있다는 것을 의미한다.(두 컨트랙트 코드의 해시값을 비교하는 것으로도 검증 가능하다.) 그러나, 모든 변수가 requestable일 필요는 없다. Request와 관련된 기능이 필요 없는 변수도 있고, 특정 변수에 대해 어떤 사용자가 request할 수 있는지에 대한 고려도 필요하기 때문이다. 예를 들어, 누구나 타인의 토큰 밸런스에 대해 request 할 수 있어서는 안되기 때문이다. 따라서 우리는 미리 컨트랙트 함수에 이러한 문제를 해결할 수 있는 로직을 배치하고자 한다. 이러한 작업은 변수가 선언 된 순서와 trieKey를 이용하여 앞에서 제시한 문제에 대해 확인 할 수 있기에 가능하다. 자세한 내용은 아래의 의사 코드에 대해 다룬 섹션에서 살펴 볼 것이다. 우선, `Enter`와 `exit`의 구체적인 과정에 대해 살펴보도록 하겠다. #### Enter ![](https://i.imgur.com/pqGxsfT.png) 1. 사용자 $U$는 $a_r$, `trieKey` 및 `trieValue`를 독립변수로 사용하여 $a_R$의 `enter()` 함수를 호출하여 $a_c$에 해당하는 $A^{r}_{s}[k]$에 enter 한다. 2. 그 다음 `enter()` 함수에서, $a_r$의 `applyRequestInRootchain()` 함수가 호출된다. 3. 2번 과정이 revert 된다면, enter request를 처리하는 과정이 중단된다. Revert 되지 않는다면 $R$에 `enterRequest` 이벤트가 생성된다. 4. $O$는 다음`requestBlock`에 `enterRequest`를 처리하는 $NA$의 트랜잭션을 담아야한다. $O$가 `enterRequest`를 블록에 제대로 담지 않는다면 $Ch$는 이에 대해`requestChallenge`를 할 수 있다. 5. 4번 과정의 트랜잭션에서 $NA$는 $a_c$의`enterRequest`에 따라`applyRequestInChildchain()` 함수를 실행한다. 그러면 $A^{c}_{s}[k]$가 $A^{c}_{s}[k]'$로 변한다. 이러한 상태 변화 과정이 올바르게 이루어지지 않으면 $Ch$는`computedChallenge`를 할 수 있다. #### Exit ![](https://i.imgur.com/vhMVtaA.png) 1. 사용자 $U$는 $a_r$에 해당하는 $A^{c}_{s}[k]$에서 exit 하기 위해, $a_r$,`trieKey` 및`trieValue`를 독립변수로 사용하여 $a_R$에서`startExit()`을 호출한다. 2. $U$의 `exitRequest`는 $a_R$에서 생성되며 $O$에 의해 다음`requestBlock`에 포함된다. $O$가 `exitRequest`를 포함하지 않는다면 ,$Ch$는 이에 대해 `requestChallenge`를 할 수 있다. 3. 다음 `requestBlock`에는 $NA$가`exitRequest` 절차에 따라 $a_c$에서`applyRequestInChildchain()`함수를 실행하는 트랜잭션이 담겨야 한다. *3번 과정*이 제대로 실행되지 않으면 $Ch$는`computationChallenge`를 할 수 있다. 4. *3번 과정*이 제대로 실행되었음에도 트랜잭션이 revert 된다면, $Ch$는 트랜잭션의 output을 증거로 삼아 `exitChallenge`를 통해 `exitRequest`를 할 수 있다. 5. *4번 과정*에서 챌린지가 발생하지 않는다면, `exitRequest`가 `finalize` 된다고 볼 수 있다. 그러나`exitChallenge`가 타당하다는 것이 증명되면 `exitRequest`의 `finalize`는 취소된다. 6. $U$는 $a_R$에서`finalizeExit()`함수를 호출하여 `exitRequest`를 `finalize` 할 수 있다. $a_R$은 $a_r$에서`applyRequestInRootchain()` 함수를 호출한다. `applyRequestInRootchain()` 함수가 문제없이 실행 된다면 $A^{r}_{s}[k]$에서 $A^{r}_{s}[k]'$로의 변경이 이루어진다. 컨트랙트 개발자는 $a_r$의`applyRequestInRootchain ()` 함수가 throw되면 안된다는 것을 **반드시** 확인 해야한다. 이러한 변경은 반드시 이루어져야 하며, exit의 유효성은`exitChallenge`에 대한 증명으로 사용될 수 있는 $a_c$의`applyRequestInChildChain()`함수에서 확인 할 수 있다. #### RootChain and Requestable Contract Child chain의 RootChain 컨트랙트는 `RequestableRootChain` 인터페이스를 가지고 있다. Requestable 컨트랙트는`enterRequest`가 storage 변수를 $a_r$에서 $a_c$로 enter하게 할 수 있다. 사용자는`rootchain.enter`로 직접 enter request를 할 수 있다. ```solidity interface RequestableRootChain { function getExitFinalized(uint256 requestId) public returns(bool); function startExit( address contractAddress, bytes32 trieKey, bytes32 trieValue ) returns (bool); // User can directly make an enter request with trieKey and trieValue. function enter( address contractAddress, bytes32 trieKey, bytes32 trieValue ) returns (bool); // Requestable contract can initialize an enter request with proper // trieKey and trieValue. This makes user not to take care of what is exact // trieKey for `balances[requestor]`. function makeEnterRequest( address requestor, bytes32 trieKey, bytes32 trieValue ) returns (bool); // finalize exits function finalizeExit() public returns (bool); } interface Requestable { // check the request is applied or not in root chain. function setRequestApplied(uint256 requestId) internal returns (bool); function getRequestApplied(uint256 requestId) internal returns (bool); // Apply storage changes in root chain for exit and enter reuqests. // For enter request, This is called in the middle of `rootchain.enter` // or `rootchain.makeEnterRequest` to initialize an enter request. // If this returns true, the enter request is queued into `enterRequests`. // For exit request, This is called in `rootchain.finalizeExit`. function applyRequestInRootChain( bool isExit, uint256 requestId, address requestor, bytes32 trieKey, bytes32 trieValue ) external returns (bool success); function applyRequestInChildChain( bool isExit, uint256 requestId, address requestor, bytes32 trieKey, bytes32 trieValue ) external returns (bool success); } ``` 간단한 requestable 토큰 컨트랙트의 예시는 다음과 같다. ```solidity contract RequestableToken is Requestable { // 3 requestable variables. address owner; // `owner` is stored at bytes32(0). uint totalSupply; // `totalSupply` is stored at bytes32(1). // `balances[addr]` is stored at keccak256(bytes32(addr), bytes32(2)). mapping(address => uint) balances; RequestableRootChain rootchain; address NULL_ADDRESS = address(0); // or 0xFF..FF // this is only called by the RootChain contract in root chain // when i) enterRequest is initialized or // ii) exitRequest is finalized function applyRequestInRootChain( bool isExit, uint256 requestId, address requestor, bytes32 trieKey, bytes32 trieValue ) external returns (bool success) { require(msg.sender == address(rootchain)); require(!getRequestApplied(requestId)); // check double applying if (isExit) { // exit must be finalized. require(rootchain.getExitFinalized(requestId)); if(bytes32(0) == trieKey) { // only owner (in child chain) can exit `owner` variable. // but it is checked in applyRequestInChildChain and exitChallenge. // set requestor as owner in root chain. owner = requestor; } else if(bytes32(1) == trieKey) { // no one can exit `totalSupply` variable. // but do nothing to return true. } else if (keccak256(requestor, 2) == trieKey) { // this checks trie key equals to `balances[requestor]`. // only token holder can exit one's token. // exiting means moving tokens from child chain to root chain. balances[requestor] += uint(trieValue); } else { // cannot exit other variables. // but do nothing to return true. } } else { // apply enter if(bytes32(0) == trieKey) { // only owner (in root chain) can enter `owner` variable. require(owner == requestor); // do nothing in root chain } else if(bytes32(1) == trieKey) { // no one can enter `totalSupply` variable. revert(); } else if (keccak256(bytes32(requestor), bytes32(2)) == trieKey) { // this checks trie key equals to `balances[requestor]`. // only token holder can enter one's token. // entering means moving tokens from root chain to child chain. require(balances[requestor] >= uint(trieValue)); balances[requestor] -= uint(trieValue); } else { // cannot apply request on other variables. revert(); } } setRequestApplied(requestId); return true; } // this is only called by NULL_ADDRESS in child chain // when i) exitRequest is initialized by startExit() or // ii) enterRequest is initialized function applyRequestInChildChain( bool isExit, uint256 requestId, address requestor, bytes32 trieKey, bytes32 trieValue ) external returns (bool success) { require(msg.sender == NULL_ADDRESS); if (isExit) { if(bytes32(0) == trieKey) { // only owner (in child chain) can exit `owner` variable. require(requestor == owner); // do nothing when exit `owner` in child chain } else if(bytes32(1) == trieKey) { // no one can exit `totalSupply` variable. revert(); } else if (keccak256(bytes32(requestor), bytes32(2)) == trieKey) { // this checks trie key equals to `balances[tokenHolder]`. // only token holder can exit one's token. // exiting means moving tokens from child chain to root chain. // revert provides a proof for `exitChallenge`. require(balances[requestor] >= uint(trieValue)); balances[requestor] -= uint(trieValue); } else { // cannot exit other variables. revert(); } } else { // apply enter if(bytes32(0) == trieKey) { // only owner (in root chain) can make enterRequest of `owner` variable. // but it is checked in applyRequestInRootChain. owner = requestor; } else if(bytes32(1) == trieKey) { // no one can enter `totalSupply` variable. } else if (keccak256(bytes32(requestor), 2) == trieKey) { // this checks trie key equals to `balances[tokenHolder]`. // only token holder can enter one's token. // entering means moving tokens from root chain to child chain. balances[requestor] += uint(trieValue); } else { // cannot apply request on other variables. } } return true; } // make an enter request for `owner` variable. function enterOwner() external returns (bool) { require(msg.sender == owner); require(rootchain.makeEnterRequest(msg.sender, bytes32(0), owner)); return true; } // make an enter request for `balances[msg.sender]`. // If user know trieKey, one can call `rootchain.enter` directly. function enterBalance(uint amount) exernal returns (bool) { // it is also checked in applyRequestInRootChain require(balances[msg.sender] >= amount); bytes32 trieKey = keccak256(bytes32(msg.sender), bytes32(2)); // this subtracts `balances[msg.sender]` by `amount` require(rootchain.makeEnterRequest(msg.sender, trieKey, amount); return true; } // User can get the trie key of one's balance and make an enter request directly. function getBalanceTrieKey(address who) public pure returns (bytes32) { return keccak256(bytes32(who), bytes32(2)); } } ``` ### Challenge #### Request Challenge `requestChallenge`를 위하여 RootChain 컨트랙트는 request의 유효성 검증을 위한 트랜잭션 데이터를 구성할 수 있다. 트랜잭션 의 `from'`은 NULL_ADDRESS, `to`는 ChildChain 컨트랙트의 주소, `value`는 0, `data`는 request 유형과 파라미터에 의해 정의되고 `nonce`는 각 request마다 순차적으로 증가한다. 실제 트랜잭션 데이터가 RootChain 컨트랙트에 의해 구성되는 데이터와 일치하지 않는다면, `requestBlock`이 이전 `nonRequestBlock`으로 되돌릴 것이다. Request에 대한 연산은 computationChallenge에 의해 검증 된다. #### NullAdress Challenge `nonRequestBlock`에 트랜잭터가 NULL_ADDRESS인 트랜잭션이 있는지 확인한다. #### Exit Challenge `requestBlock`에 포함된 `exitRequest`가 *revert* 되었다면, 해당 `exitRequest` 는 **반드시** RootChain 컨트랙트에서 삭제되어야 한다. Invalid exitRequest에 대한 `exitChallenge` 절차는 revert 된 트랜잭션을 증거로 삼아 처리된다. 그러나 exit request에 대해 챌린지하기 전에, 처리된 request를 포함하고 있는 블록은 일단 finalize 되어야 한다. 왜냐하면, invalid한 블록에서는 트랜잭션이 revert될 수 있고, valid한 블록에서는 revert 되지 않을 것이기 때문이다. 그렇기 때문에 exit challenge는 `requestBlock`이 finalize 된 후에 시작 될 수 있다. #### Computation Challenge computationChallenge는 operator가 트랜잭션을 올바르게 실행했는지를 검증한다. 즉 상태가 미리 정의 된 방식으로 변경 되었는지를 확인하는 것이다(예: 스마트 컨트랙트 바이트 코드). Operator가 잘못된 `stateRoot`를 제출하면, 이는 상태 변환이 올바르게 이루어지지 않았다고 판단할 근거가 된다. 그러면 `txData`,`preStateRoot` 및`postStateRoot`와 함께 TrueBit의 verification game과 유사한 방법을 사용하여 챌린지 될 수 있다. $$postState = STF_{tx}(preState, TX_i)$$ $$postState = postTransactionalStates[i]$$ $$ preState=\begin{cases} postTransactionalStates[i-1] & \text{if}\ i>0 \\ previousStateRoot & \text{otherwise}\end{cases}$$ RootChain 컨트랙트에서 $STF_{tx}$를 시뮬레이션하고 output을 이미 제출 된 output과 비교하여 연산이 올바르게 이루어졌는지에 대한 여부를 확인할 수 있다. #### Verification Game TrueBit은 outsource된 연산을 검증하기 위한 방법으로 *verification game*을 제안했다. 그러나 TrueBit이 제안한 게임의 마지막 단계는 이더리움에서 연산을 한 번 수행하고 실제 output과 예상 output을 비교하는 방법을 사용한다. 하지만 우리는 Ohalo와 PARSEC에서 구현해 왔던 EVM 내부에서 EVM을 실행하는 스마트 컨트랙트인 solevm을 사용하여 연산 결과를 검증하고자 한다. ![](https://i.imgur.com/BVFY09y.png) 그러나 [수수료 위임 체인](https://hackmd.io/s/SkxNKAXU7)을 사용하려면 EVM을 통한 트랜잭션 실행을 포괄해야 한다. ### References - [Joseph Poon and Vitalik Buterin, Plasma: Scalable Autonomous Smart Contracts](https://plasma.io/) - [Minimal Viable Plasma](https://ethresear.ch/t/minimal-viable-plasma/426) - [Plasma Cash](https://ethresear.ch/t/plasma-cash-plasma-with-much-less-per-user-data-checking/1298) - [Plasma XT](https://ethresear.ch/t/plasma-xt-plasma-cash-with-much-less-per-user-data-checking/) - [Cryptoeconomic aggregated signature](https://ethresear.ch/t/cryptoeconomic-signature-aggregation/) - [PARSEC Labs, PLASMA - FROM MVP TO GENERAL COMPUTATION](https://parseclabs.org/files/plasma-computation.pdf) - [Why Smart Contracts are NOT feasible on Plasma](https://ethresear.ch/t/why-smart-contracts-are-not-feasible-on-plasma/2598) - [TrueBit, A scalable verification solution for blockchains ](https://people.cs.uchicago.edu/~teutsch/papers/truebit.pdf)