# PoC1 : EVM Compatible Gas Delegated Transaction Execution Architecture(국문) Kevin Jeong(정순형, 철학자)(kevin.j@onther.io, Onther Inc.) Thomas Shin(신건우)(thomas.s@onther.io, Onther Inc.) Jason Hwang(황재승)(jason.k@onther.io, Onther Inc.) Carl Park(박주형, 4000D)(carl.p@onther.io, Onther Inc.) :::info [English Version Link](https://hackmd.io/s/SkxNKAXU7) ::: # 초록(Abstract) PoC1은 유저/개발 친화적인 이더리움 가상 머신(Ethereum Virtual Machine)의 실행 모델로써 이더리움에서 사용된 서비스 거부 공격(DoS Attack)을 막기 위한 가스 수수료 시스템(Gas)을 그대로 유지하면서도, 실행 과정에서 이에 대한 부담을 블록체인에 정의된 제3의 자율 에이전트(Autonomous Agent)인 스마트 컨트랙트를 통해 지정된 어카운트에게 위임할 수 있는 거래처리 공정(procedure)을 제시하는 것을 목표로 한다. PoC1은 기존의 이더리움 가상 머신을 이용한 트랜잭션 실행 모델에서 지불된 가스 수수료와 value(ETH)를 계정 잔액(State Balance)에서만 차감하는 단선적 구조에서, i)tx.value는 계정 잔액에서 ii)가스 수수료는 스테미나(stamina)라고 불리는 별도로 정의된 컨트랙트의 잔액상태에서 차감하는 복선적인 구조로의 개선을 이뤄냈다. 또한 이렇게 위임된 수수료 소모량을 지속적으로 추적-관리하고, 일정한 기간이 지나면 다시 이를 재생해 줌으로써 수수료 부담 정책은 마치 예치된 정액제(Staked-and-Fixed Charge)의 형태를 띄게 된다. 이를 통해 유저들은 단 한번만 비용을 예치하면, 일정한 사용량 내에서는 어플리케이션 이용간 불필요한 잔액 유지 행위를 지속 할 필요가 없도록 만든다. 또한 서비스 제공자들에게는 더욱 유저/개발 친화적인 탈중앙화 어플리케이션(DApp)을 만들 수 있는 환경을 제공함으로써 블록체인에 익숙하지 않은 사용자들을 끌어들일 수 있는 킬러앱(Killer App)의 등장을 가속시켜 탈중앙화된 생태계가 확장해 나가는데 도움을 줄 것으로 판단된다. # 1장 서론 ## 1.1 프로젝트의 배경과 목적 튜링 완전한 블록체인의 목표를 달성 하면서도 네트워크의 부정한 사용을 방지하기 위해 이더리움은 모든 컴퓨팅 연산에 수수료를 부과한다. 이러한 수수료는 가스라는 특별한 단위로 계산되며, 이더리움 블록체인에서 이뤄지는 모든 연산은 “가스”라는 [시-공간 비용](https://medium.com/onther-tech/plasma-mvp-audit-%EB%85%B9%EC%B7%A8%EB%A1%9D-script-166a2c5012b4)을 지불하게 되어 있다. 이더리움의 모든 트랜잭션은 가스 제한(Gas Limit)과 가스 가격(Gas Price) 필드를 가지고 있다. 여기서의 가스 제한은 해당 트랜잭션이 얼마나 많고 복잡한 연산을 수행하느냐에 대한 예산(budget)으로서의 특징을 지니고 있으며, 가스 가격(Gas Price)은 이러한 연산을 블록에 포함하기 위해 마이너들에게 지불하는 입찰가로서의 성격을 가지고 있다. 그렇기 때문에 만약 트랜잭션이 수행되는 과정에서 소모한 가스 사용량(Gas Used)이 설정된 가스 제한(Gas Limit)보다 높을 경우, 이 트랜잭션은 유효하지 않은(invalid) 트랜잭션이 된다. 더해서 가스 가격(Gas Price)이 충분히 (블록에 포함되기를 대기하는 다른 트랜잭션에 비하여 상대적으로)높지 않을 경우 마이너들은 해당 트랜잭션을 블록에 포함시키지 않을 가능성이 높아진다. 이더리움 블록체인은 이러한 형태의 수수료 모델을 통해서 서비스 거부공격(DoS Attack)을 방지하고 `블록체인의 상태변화에 대한 권리(right of state transition)`라는 한정된 자원에 대한 분배문제를 해결했지만, 각 실행자(Transactor)가 트랜잭션을 전송할 때마다 이러한 비용을 부담해야 한다. 중요한 점은 트랜잭션을 생성하는 계정(Transactor)이 가스 사용량(gas used) * 가스 가격(gas price) 이상의 이더를 트랜잭션 생성간 매번 지불해야 하고, 이를 위해 `일정량의 잔액수준(level of balance)`을 상시 유지해야 한다는 점이다. 이는 이더리움 블록체인을 통해 만들어지는 탈중앙화 어플리케이션(DApp)들의 사용성을 현저히 떨어뜨렸다. PoC1에서는 트랜잭션 수수료의 부담에 관한 문제로 인한 사용성 저하에 대한 문제를 개선한 트랜잭션 실행 레벨에서 처리하는 모델(Execution Model)을 설계하고자 한다. 이를 통해 유저 친화적인(접근성/사용성 관점에서) 어플리케이션 개발을 위한 토양이 마련되기를 기대한다. ## 1.2 연구 방법 페이퍼는 PoC1 구현을 위한 주요 명세를 정의하고, 이에 대한 구체적인 구현을 코드레벨에서 서술하는데 중점을 두고 있다. 구현 코드의 경우 이더리움의 고-언어(golang) 구현체인 [go-ethereum](https://github.com/ethereum/go-ethereum)과 파이썬(python) 구현체인 [py-evm](https://github.com/ethereum/py-evm)을 분기(Forking)하여 가스비를 위임한 실행 아키텍처에 대해 논의하는 방식으로 진행한다. ## 1.3 약칭 및 용어 정의 약칭은 이더리움 옐로우 페이퍼의 약칭을 필요한 부분을 발췌해서 사용하고, 필요시 재정의 하도록 한다. 또한 PoC1에서 정의된 단어들은 다음과 같다. --- - ERC20: ERC-20은 Ethereum 블록 체인에서 토큰을 구현할 때 사용하는 스마트 컨트랙트 기술 표준. - 계정 잔액(state balance) : 이더리움 블록체인의 자료구조 중 상태(State)에 기록 되어있는 잔액(balance) - 계정(Account) : 이더리움 블록체인에서의 사용자 식별 단위 - 널-계정(NULL_ADDRESS) - 트랜잭션 생성 계정(transactor): 트랜잭션을 전송하는 계정을 의미 - 가스(gas) : 이더리움의 수수료 단위 - 가스 한계(gas limit) : 트랜잭션 생성시 지정한 최대 가스 소모량 - 트랜잭션 지불 예산(upfront cost) : 이더리움 가상머신이 실행되기 전에 부과되는 비용`value + gasLimit * gasPrice`로 계산된다.(Expenses that are charged before executing EVM.) - 가스 예산(gas-upfront cost) : 트랜잭션 지불예산 중 내재적 가스(instrisic gas)로 소모되는 부분. `tx.gasLimit * tx.gasPrice`로 계산된다. - 금액 예산(value-upfront cost) : 트랜잭션 지불예산 - 가스예산 = `tx.value`와 같다. - 환불 가스(refunded gas) : 가스한계 - 트랜잭션이 실제 소모한 가스(가스 - 트랜잭선 소모 가스) --- - 수수료 수임 계정(delegatee): 다른 계정의 내재적 가스(intrisic gas)를 대신 납부하는 계정 - 수수료 위임 계정(delegator): 수임계정(delegatee)에게 트랜잭션 수수료를 위임하여 계정 잔액(State Balance) 없이 트랜잭션을 발생시킬 수 있는 계정, 수임계정(delegatee)에 의해 지정된다. - 수수료 위임 쌍, 스테미나 쌍(fee-delegate pair, stamina pair): 스테미나 컨트랙트에서 수임계정이 위임계정을 지정하여 쌍을 이루면, 이를 <수수료 위임 쌍> 혹은 <스테미나 쌍>이라 한다. --- - 스테미나(Stamina): 수임계정이 위임계정의 가스비를 구매할 때 사용되는 위임 계정의 예치된 계정 잔액(deposited account balance) - 스테미나 컨트랙트(Stamina Contract) : 위임된 가스를 스테미나를 이용해 처리하는 절차가 기록된 탈중앙화된 계약 코드 - 예치 계정(depositor account; depositor) : 수임계정에 스테미나를 충전한 계정 - 스테미나 예치(stamina deposit) : 수임계정에 스테미나를 충전하는 행위 - 스테미나 출금(stamina withdraw) : 충전된 스테미나를 빼고, 그만큼 다른 계정 잔액을 채우는 행위 - 스테미나 환불(stamina refund) : 트랜잭션의 가스 한계에서 실제 소모된 가스 사용량을 제하고 돌려받은 스테미나 - 스테미나 차감(stamina substract) : 트랜잭션 실행 과정에서 널-계정이 가스예산 만큼의 스테미나를 수임계정에서 제하는 행위 - 스테미나 회복(stamina recover) : 스테미나 회복기간이 도래하여 최초 예치된 스테미나가 다시 채워지는 것 - 스테미나 회복(Stamina Recover): 수임계정의 스테미나를 다음 기간(Epoch) 때 예치계정(depositor)이 수임계정(delegatee)에게 예치(deposit)한 계정 잔액 만큼 스테미나를 회복시키는 것을 말한다. 스테미나 회복(recover) 양은 수임계정에게 최초 예치한 계정 잔액의 총량과 같다. - 스테미나 회복 주기(Stamina Epoch Length): 스테미나가 회복되기 위해서는 일정 기간이 지나야 하는데, 이 기간을 스테미나 회복 주기(Stamina Epoch Length)라고 한다. 다음 기간(epoch)은 스테미나 충전 주기를 충족한 기간을 말한다. - 주기(Epoch) : 스테미나 회복 주기의 기간 단위 --- <!-- 납부 될 플라즈마 체인에서 수수료가 위임된 트랜잭션에서 수수료로 사용되는 예치 잔액 수준 --> # 2장 시스템 설계 이 장에서 설계하는 시스템은 크게 `i)위임된 가스비에 대한 제반사항을 다루는 컨트랙트`와 `ii)트랜잭션이 처리되는 과정`에 대한 부분으로 나뉘어 기술된다. ## 2.1 위임된 가스 처리(Delegated Gas Treatment) ### 2.1.1 스테미나(Stamina) `스테미나(Stamina)`는 위임계정(delegator)이 본 트랜잭션 실행 모델을 구동시킬 때 필요한 가스비를 수임계정(delegatee)에게 청구할 때 사용된다. 즉 위임계정(delegator)의 가스비를 수임계정(delegatee)이 스테미나(Stamina)의 형태로 구매하여 위임계정의 가스비 부담을 대신하게 된다. 스테미나는 스테미나 컨트랙트(Stamina contract)에 계정 잔액(state balance)을 예치(deposit)함으로써 얻을 수 있는데, 수임계정의 스테미나를 예치(deposit)한 계정을 `예치 계정(depositor account)`이라 부른다. 수임계정의 스테미나는 위임계정의 가스비를 구매하면서 차감된다. 차감된 스테미나는 영원히 사라지는 것이 아니라 일정 기간이 지나면 회복(recover)되는데, 이를 PoC1에서는 `스테미나 회복(stamina recover)`이라 부른다. 스테미나가 회복 되기 위해서는 일정 기간이 필요한데, 이 기간을 `회복 기간(RECOVER EPOCH LENGTH)`이라 부르고, 다음 기간(EPOCH)에서 수임계정에 총 예치된 양만큼의 스테미나가 새롭게 충전된다. 이렇게 충전된 스테미나 잔액은 다음과 같이 표현할 수 있다.($a$는 어카운트) $$ a_{stm} \tag{1} $$ ### 2.1.2 스테미나 쌍 변환(stamina pair transformation) 위임계정은 $D_{tor}$로, 수임계정은 $D_{tee}$로 표기하고, <위임계정>을 인자로 <수임계정>꺼내는 변환(transformation)을 $ST_{pair}F()$라 정의하면, 둘의 관계는 다음으로 표기될 수 있다. $$ ST_{pair}F(D_{tor}) = D_{tee} \tag{2} $$ <!--그리고 모든 계정 공간을 $a$로 표기했을때, 이들간의 관계는 다음으로 표현할 수 있다. $$ D_{tor} \in a \\ D_{tee} \in a \\ D_{tor} \cap D_{tee} \neq \varnothing $$ 이렇게 만들어진 스테미나 쌍을 $D_{pair}$라 표시하고, 다음으로 정의한다. $$ D_{pair} \equiv (D_{tor} \cup D_{tee}) $$ --> ### 2.1.3 스테미나 컨트랙트 스테미나 컨트랙트는 수수료 위임 모델에 있어서 중요한 컨트랙트다. 스테미나 컨트랙트는 다음 기능을 제공한다. 1. 위임계정(delegator) 지정 2. 스테미나 증감(increase/decrease) 3. 스테미나 환불/차감/회복(refund/subtract/recover) #### 2.1.3.1 위임계정 지정 수임 계정(delegatee)은 위임 계정(delegator)을 지정할 수 있다. 오직 수임 계정(delegatee)만 위임 계정(delegator)을 지정할 수 있으며, 이 반대로 위임계정(delegator)은 수임 계정(delegatee)을 지정할 수 없다. 더해서 수임 계정(delegatee)은 여럿의 위임 계정(delegator)을 지정할 수 있다. ![](https://www.dropbox.com/s/6bn8fkb0li7qjc1/set_delegator%281%29.png?raw=1) 수임계정이 위임계정을 지정하게 되면 쌍(pair)이 형성되는데 이를 `스테미나 쌍(stamina pair)` 또는 `수수료 위임 쌍(fee-delegate pair)`이라 부른다. 수임계정(delegatee)는 위임계정(delegator)을 지정하기 위해서 스테미나 컨트랙트의 `setDelegator()` 함수를 호출한다. ```solidity function setDelegator(address delegator) external onlyInitialized returns (bool) { address oldDelegatee = _delegatee[delegator]; _delegatee[delegator] = msg.sender; emit DelegateeChanged(delegator, oldDelegatee, msg.sender); return true; } ``` #### 2.1.3.2 스테미나 증감(increase/decrease) 수임계정은 위임계정의 가스비를 구매하기 위해서 스테미나가 필요하다. 수임계정은 가지고 있는 스테미나의 양만큼 위임계정의 가스비를 대신 부담할 수 있다. 수임계정의 스테미나 소모량은 위임계정의 수 또는 트랜잭션 가스비와 비례할 가능성이 높다. 따라서 예치계정은 위임계정의 수와 트랜잭션 가스비에 맞게 수임계정의 스테미나를 늘리거나 줄일 것이다. 스테미나를 늘리기 위해서 예치계정(depositor)은 스테미나 컨트랙트의 `deposit()` 함수를 호출해야 한다. 예치계정은 예치(deposit)할 만큼의 계정잔액을 예치하고 수임계정은 예치한 만큼의 계정 잔액(state balance)을 추가적인 스테미나로 보유하게 된다. ```solidity function deposit(address delegatee) external payable onlyInitialized returns (bool) { require(msg.value >= MIN_DEPOSIT); uint totalDeposit = _total_deposit[delegatee]; uint deposit = _deposit[msg.sender][delegatee]; uint stamina = _stamina[delegatee]; require(totalDeposit + msg.value > totalDeposit); require(deposit + msg.value > deposit); require(stamina + msg.value > stamina); _total_deposit[delegatee] = totalDeposit + msg.value; _deposit[msg.sender][delegatee] = deposit + msg.value; _stamina[delegatee] = stamina + msg.value; if (_last_recovery_block[delegatee] == 0) { _last_recovery_block[delegatee] = block.number; } emit Deposited(msg.sender, delegatee, msg.value); return true; } ``` 이와 반대로 수임계정의 스테미나를 출금(withdraw) 받기 위해서는 스테미나 컨트랙트의 `withdraw()` 함수(더 정확하게는 `requestWithdrawl()`, `withdraw()` 함수)를 사용한다. 이 때 스테미나는 줄어들고 줄어든 스테미나만큼 수임계정에게 계정 잔액이 출금(withdraw)된다. 수임계정이 스테미나를 출금(withdraw)하기 위해서 두 단계([Favor pull over push payments](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls))를 거친다. 1. `requestWithdrawl()` 함수 호출 : 먼저 예치계정(depositor)의 컨트랙트의 `requestWithdrawal()` 함수를 호출한다. 이를 호출하게 되면 수임 계정의 스테미나는 요청한 양만큼 차감되고 이에 대한 기록은 스테미나 컨트랙트에 남게 된다. 2. `withdraw()` 함수 호출 : 예치계정은 `withdraw()` 함수를 호출하여 `requestWithdrawl()` 함수를 통해 기록된 정보를 이용해 차감한 스테미나만큼 계정 잔액을 채운다. ```solidity function requestWithdrawal(address delegatee, uint amount) external onlyInitialized returns (bool) { require(amount > 0); uint totalDeposit = _total_deposit[delegatee]; uint deposit = _deposit[msg.sender][delegatee]; uint stamina = _stamina[delegatee]; require(deposit > 0); require(totalDeposit - amount < totalDeposit); require(deposit - amount < deposit); _total_deposit[delegatee] = totalDeposit - amount; _deposit[msg.sender][delegatee] = deposit - amount; if (stamina > amount) { _stamina[delegatee] = stamina - amount; } else { _stamina[delegatee] = 0; } Withdrawal[] storage withdrawals = _withdrawal[msg.sender]; uint withdrawalIndex = withdrawals.length; Withdrawal storage withdrawal = withdrawals[withdrawals.length++]; withdrawal.amount = uint128(amount); withdrawal.requestBlockNumber = uint128(block.number); withdrawal.delegatee = delegatee; emit WithdrawalRequested(msg.sender, delegatee, amount, block.number, withdrawalIndex); return true; } function withdraw() external returns (bool) { Withdrawal[] storage withdrawals = _withdrawal[msg.sender]; require(withdrawals.length > 0); uint lastWithdrawalIndex = _last_processed_withdrawal[msg.sender]; uint withdrawalIndex; if (lastWithdrawalIndex == 0 && !withdrawals[0].processed) { withdrawalIndex = 0; } else if (lastWithdrawalIndex == 0) { // lastWithdrawalIndex == 0 && withdrawals[0].processed require(withdrawals.length >= 2); withdrawalIndex = 1; } else { withdrawalIndex = lastWithdrawalIndex + 1; } require(withdrawalIndex < withdrawals.length); Withdrawal storage withdrawal = _withdrawal[msg.sender][withdrawalIndex]; require(!withdrawal.processed); require(withdrawal.requestBlockNumber + WITHDRAWAL_DELAY <= block.number); uint amount = uint(withdrawal.amount); withdrawal.processed = true; _last_processed_withdrawal[msg.sender] = withdrawalIndex; msg.sender.transfer(amount); emit Withdrawn(msg.sender, withdrawal.delegatee, amount, withdrawalIndex); return true; } ``` #### 2.1.3.3 스테미나 차감/환불/회복(subtract/refund/recover) 이더리움에서는 트랜잭션을 처리하기 위해서 거래 생성자(transactor)의 잔액(Balance)으로 트랜잭션의 가스비를 구매하고 구매한 가스비로 트랜잭션을 처리 후에 남은 가스비는 다시 거래 생성자(transactor)에게 계정 잔액(ETH)을 되돌려준다. 이와 마찬가지로 PoC1 에서도 위임계정의 가스비를 구매하기 위해서 수임계정의 스테미나를 차감하고 트랜잭션 실행 이후에 남은 스테미나를 수임계정에게 되돌려 준다. 위임계정이 수임계정의 가스비를 구매할 때 수임계정의 스테미나는 차감된다. 더 정확하게 말하면 위임계정이 생성한 트랜잭션의 가스 예산(gas-upfront cost)만큼 스테미나를 차감한 후에 수행 후 남은 스테미나를 환불(refund)해준다.(수식참고번호). 스테미나의 차감은 PoC1이 직접 스테미나 컨트랙트의 `subtractStamina()` 함수를 호출함으로써 이루어진다. ```solidity function subtractStamina(address delegatee, uint amount) external onlyChain returns (bool) { uint stamina = _stamina[delegatee]; require(stamina - amount < stamina); _stamina[delegatee] = stamina - amount; return true; } } ``` `subtractStamina()` 함수는 onlyChain modifier를 가진다. 이 onlyChain modifier를 가지는 함수는 NULL_ADDRESS만이 직접 호출할 수 있고 다른 계정에서는 직접 호출할 수 없다. ```solidity modifier onlyChain() { require(msg.sender == address(0)); _; } ``` 스테미나로 가스비를 구매하여 트랜잭션을 처리한 후 남은 가스비는 수임계정에게 환불(refund)된다. 이 또한 널-계정(NULL_ADDRESS)이 스테미나 컨트랙트 함수를 호출함으로써 이루어지는데 `addStamina()` 함수를 호출하게 된다. `addStamina()` 함수 안에는 회복량(recoverery amount)을 확인하는 로직도 포함되어 있다. ```solidity function addStamina(address delegatee, uint amount) external onlyChain returns (bool) { if (_last_recovery_block[delegatee] + RECOVER_EPOCH_LENGTH <= block.number) { _stamina[delegatee] = _total_deposit[delegatee]; _last_recovery_block[delegatee] = block.number; _num_recovery[delegatee] += 1; return true; } uint totalDeposit = _total_deposit[delegatee]; uint stamina = _stamina[delegatee]; require(stamina + amount > stamina); uint targetBalance = stamina + amount; if (targetBalance > totalDeposit) _stamina[delegatee] = totalDeposit; else _stamina[delegatee] = targetBalance; return true; } ``` 수임계정(delegatee)의 스테미나는 위임계정(delegator)의 가스비를 구매하면서 결국 고갈될 것이다. 하지만 고갈된 스테미나는 다음 회복 기간(EPOCH)에 새로 충전된다. 즉 수임계정은 가지고 있는 스테미나를 다 사용해도 다음 회복기간(EPOCH)에서 새로 스테미나가 충전되기 때문에 스테미나를 재사용할 수 있다. 스테미나 회복은 스테미나가 환불될 때 자동으로 수행된다. 스테미나를 환불하는 과정에서 회복 기간이 도래했는지(RECOVER EPOCH LENGTH) 체크한 후 해당될 경우 스테미나를 다시 회복해준다. 회복되는 스테미나의 양은 수임자가 예치한 계정잔액의 총량과 같다. ```solidity if (_last_recovery_block[delegatee] + RECOVER_EPOCH_LENGTH <= block.number) { _stamina[delegatee] = _total_deposit[delegatee]; _last_recovery_block[delegatee] = block.number; _num_recovery[delegatee] += 1; return true; } ``` ## 2.2 트랜잭션 실행(Transaction Execution) ### 2.2.1 트랜잭션 유효성(Transaction Validity) Gav Wood는 ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER[1]에서 트랜잭션의 내재적 유효성(intrisic validity)을 테스트 하기 위한 5가지의 조건을 정의했다. 1. 트랜잭션은 RLP로 인코딩 되어야 하며, 남는 바이트가 없어야 한다(The transaction is well-formed RLP, with no additional trailing bytes;) 2. 트랜잭션의 서명은 유효해야 한다(the transaction signature is valid;) 3. 트랜잭션 논스가 유효해야 하며, 이는 계정의 현재 논스값과 같아야 한다(the transaction nonce is valid (equivalent to the transactor account’s current nonce);) 4. 트랜잭션에서 사용한 가스 리밋은 내재가스, $g_0$보다 커야 한다.(the gas limit is no smaller than the intrinsic gas, $g_0$, used by the transaction;) 5. 트랜잭션 계정의 잔액은 지불 예산을 감당하기 위해 $v_0$이상을 보유해야 한다.(the transactor account balance contains at least the cost, $v_0$, required in up-front payment.) PoC1은 5번 조건을 다음으로 수정한다. 5. 트랜잭션 생성자(transactor)의 잔액(balance)은 금액 예산(value-upfront cost), $v_{v0}$, 이상이어야 한다. 6. <트랜잭션 생성자(transactor)의 잔액> 혹은 <스테미나 쌍에 정의된 트랜잭션 생성자와 대응하는 수임계정의 스테미나 잔액>은 가스 예산(gas-upfront const), $v_{v0}$, 이상이어야 한다. <!-- g_0는 재내적 스테미나와 관련 없음 트랜잭션 생성자가 스테미나 쌍(stamina pair)의 delegator일 경우 $g_0*P_0$는 내재적 스테미나(intrisic stamina) ${st}_0$ 가 된다. $$ \begin{cases} g_0 * T_p - gasUsed = {st}_0, & \text{if $T_s$ $\in$ $ST$} \\ g_0 * T_p - gasUsed \neq {st}_0, & \text{if $T_s$ $\notin$ $ST$} \end{cases} $$ --> 트랜잭션 예산(upfront cost)이 다음으로 정의된다고 했을 때, $$ v_0 \equiv T_g T_p + T_v \tag{3} $$ 본 페이퍼에서는 위의 식을 가스 예산(gas-upfront cost)과 금액 예산(value-upfront cost)으로 구분한다. $$ \begin{align} v_{g0} \equiv T_g T_p \tag{4} \\ v_{v0} \equiv T_v \tag{5} \end{align} $$ 이 때 트랜잭션 유효성(Transaction Validity)은 다음으로 표현할 수 있다. $$ \begin{align} S(T) \neq \varnothing \quad \land \tag{6} \\ T_n = \sigma[S(T)]_n \quad \land \tag{7} \\ g_0 \leqslant T_g \quad \land \tag{8} \\ v_{v0} \leqslant \sigma[S(T)]_b \quad \land \tag{9} \\ \bigl((v_{v0} + v_{g0} \leqslant \sigma[S(T)]_b) \lor (v_{g0} \leqslant [ST_{pair}F(S(T))]_{stm})\bigr) \quad\tag{10} \end{align} $$ > [name=philosopher] > (10)식을 해석하면 > 계정 잔액이 모든 upfront cost를 감당하거나, 스테미나가 gas-upfront cost를 감당해야 한다. ### 2.2.2 비위임 실행(normal execution) 비위임 실행은 기존의 이더리움 체인에서의 트랜잭션 처리 과정과 같으며, 다음의 절차를 거친다. 1. 트랜잭션 실행자(transactor)의 잔액을 조회 : 트랜잭션을 생성한 계정의 잔액을 state.balance에서 불러온다. 2. 트랜잭션 지불 예산(upfront cost) 감당 가능여부를 판단 3. 지불 예산(upfront cost) 차감 4. 가상머신실행 5. 환불 가스(refunded gas) 지급 ### 2.2.3 위임 실행(delegated execution) 위임실행의 경우 다음의 과정을 거친다. - 트랜잭션 생성 계정(transactor)이 위임할 수 있는 수임계정이 존재하는지 확인한다(위임계정이 스테미나 쌍에 포함되어 있는지 여부를 확인한다.) 1. (수임계정이 있을 경우) 1. 수임계정의 가스 예산(gas-upfront cost) 감당 가능 여부를 판단 2. 수임계정에게서 가스예산에 해당하는 스테미나를 차감 3. 트랜잭션 생성 계정의 잔액에서 금액 예산(value-upfront cost)을 차감 4. 가상머신 실행 5. 환불 가스(refunded gas)만큼의 스테미나를 수임계정에게 환불 2. (수임계정이 없을 경우) 1. 비위임 실행(2.2.2 상술) ### 2.2.4 의사코드 의사코드는 2.2.3의 위임실행 과정을 구체화하고 있다.스테미나 컨트랙트는 `staminaContract객체`로 표현하였다. `tx_traditional_execute()`함수는 기존의 이더리움(traditional ethereum)의 비위임 실행에 대한 의사코드를, `tx_delegated_execute()`함수는 위임 실행에 대한 의사코드를 담고있다. ```python def tx_traditional_execute(from, to, value, gasLimit, gasPrice, data) : # 1. check `from` can pay upfront cost balance = getBalance(from) assert balance >= value + gasLimit * gasPrice # 2. substract upfront cost(tx.value + tx.gasLimit * tx.gasPrice) substractBalance(from, balance - value - gasLimit * gasPrice) # 3. execute EVM executeVM(from, to, value, gasLimit, gasPrice, data) # 4. collect refunded gas addBalance(from, gasRemained * gasPrice) def tx_delegated_execute(from, to, value, gasLimit, gasPrice, data) : # 0. check if (delegator, delegatee) pair exists. execute EVM. # getDelegatee() returns <delegatee address> # or it returns <None> delegatee = staminaContract.getDelegateeAddress(from) # 1. case where delegatee exist # Check, if `to` has delegatee check upfront cost(only gasLimit * gasPrice) if delegatee and staminaContract.balanceOf(delegatee) >= gasLimit * gasPrice: # 1-1. check if `from` can pay upfront cost(only value) assert getBalance(from) >= value # 1-2. subtract upfront cost(only gasLimit * gasPrice) from delegatee staminaContract.subtractBalance(delegatee, gasLimit * gasPrice) # 1-3. substract upfront cost(only value) substractBalance(from, value) # 1-4. execute EVM executeVM(from, to, value, gasLimit, gasPrice, data) # 1-5. collect refunded gas to delegatee staminaContract.addBalance(delegatee, gasRemained * gasPrice) # 2. case where delegatee does not exist else: # Execute tx tranditional way tx_traditional_execute(from, to, value, gasLimit, gasPrice, data) ``` ### 2.2.5 기존 이더리움의 실행모델과 비교 | | Traditional Ethereum | Delegated Model | 비고 | | ---------------------- | --------------------- | ---------------| -----------| | upfront cost 차감 대상 | transactor | transactor or delegatee | | | gas-upfront 차감 대상 | transactor | transactor or delegatee | | | value-upfront 차감 대상 | transactor | transactor | | | Minimum EVM Execution | 1 | 3 | | # 3장 : 구현체 ## 3.1 go-ethereum, py-evm 구현 이 페이퍼에서 제시된 스펙은 실제 go-ethereum과 py-evm을 이용해 구현되었으며, 구체적인 코드와 스펙설명은 다음 페이퍼에 기술되어 있다. https://hackmd.io/s/HJrS5IxIm <!-- # 3장 구현 및 테스트 Genesis 블록에 Stamina 컨트랙트를 넣어두고 내부적으로 컨트랙트 함수를 호출하여 가스 비용을 위임하는 것을 목적으로 하였다. 이를 위해 EVM 실행 함수를 사용하여 컨트랙트 실행 함수를 만들었고 tx-execution 및 state-transition 단계에 적용하였다. ## 3.1 go-ethereum 구현 ### 3.1.1 Stamina 컨트랙트 관련 parameter 설정 Stamina 컨트랙트 주소 및 bytecode 설정 ```go var StaminaContractAddressHex = "0x000000000000000000000000000000000000dead" var StaminaContractAddress = common.HexToAddress(StaminaContractAddressHex) // deployed bytecode var StaminaContractBin = "0x6080604052600436106100d0576000f..." ``` ### 3.1.2 Stamina 컨트랙트 실행 함수 구현 ### 3.1.3 tx-pool 검증 (static evm 생성) ### 3.1.4 Genesis블록 환경 구성(Stamina 컨트랙트 구성) ### 3.1.5 tx-execution / tx-pool / tx-validation 수정 ## 3.2 py-evm 구현 ### 3.2.1 py-evm 개요 및 구조 ### 3.2.2 Stamina 컨트랙트 실행함수 구현 및 EVM 실행 함수 구현 ### 3.2.3 Script 파일 작성 ### 3.2.4 state-transition 수정 --> # 4장 결론 이더리움은 튜링 완전한 블록체인, 네트워크의 부정한 사용 방지라는 두 가지 목적을 달성하기 위해 이더리움 블록체인 위에서 발생하는 모든 컴퓨팅 연산에 수수료를 부과하고 있다. 이 수수료는 가스라 불리며, 이더리움의 모든 사용자들은 이 가스를 트랜잭션의 수수료로 지불한다. 모든 트랜잭션 마다 수수료를 지불함으로써 이더리움은 DoS(Denial of Service)공격을 방지할 수 있지만, 이더리움 위에서 동작하는 서비스를 이용하기 위해서는 항상 트랜잭션 수수료 지불을 위해 잔액을 확인하고 부족할 경우 잔액을 충전해야 한다는 불편함을 갖고 있다. 이러한 불편함은 스팀이나 이오스와 같이 매번 수수료를 지불하지 않아도 되는 블록체인들이 생겨나는 계기가 되었다. 물론 트랜잭션 수수료로 인한 사용성 저하가 스팀이나 이오스가 이더리움에 제기하는 문제점의 전부는 아니지만 PoC1에서는 우선 사용성에 대한 문제를 해결하고자 사용자가 트랜잭션 수수료에 대해 걱정할 필요 없고 DoS와 같은 네트워크 공격에도 대응할 수 있는 실행 모델을 제시했다. PoC1의 가장 큰 특징은 수수료 위임 쌍(fee-delegation pair)간의 트랜잭션 수수료의 위임이다. 수수료 위임을 위해 PoC1에서는 기존 가스 시스템에 스테미나라는 새로운 개념을 추가하였다. 인간이 활동을 하면 줄어들고 휴식을 취하면 회복되는 스테미나처럼 PoC1에서 이야기하는 스테미나 또한 사용하면 줄어들고 일정 기간이 지나면 회복하는 특성을 가지고 있다. 스태미나를 사용하기 위해서는 수임계정(delegatee)이 위임계정(delegator)을 지정하여 스테미나 쌍(stamina pair)을 형성 해야한다. 수임계정(delegatee)은 스테미나를 미리 예치(deposit) 해놓아야 하며, 스테미나 쌍 설정과 스테미나 예치가 완료되었다면, 위임계정들은 수수료에 대한 직접적인 걱정없이 서비스를 이용할 수 있다. 위임계정(delegator)에 의해 발생하는 트랜잭션이 많을 수록, 수임계정은 더 많은 스테미나가 필요할 것이다. 스테미나가 바닥나면, 위임계정들은 본인의 계정에 있는 잔액을 트랜잭션 수수료로 지불할 수 있고, 어찌되었던 모든 트랜잭션에 수수료가 부과되기 때문에 DoS와 같은 네트워크 공격을 방지 할 수 있다. 또한 수임계정은 위임계정의 이상 행위를 발견했을때 쌍 지정을 해제함으로써 악의를 가진 위임계정이 수임계정의 스태미나를 소모시켜 버리는 것을 막을 수 있다. 만약 특정한 목적을 위해 체인 상에서 운영되는 탈중앙화 어플리케이션(dapp)이 있다고 했을 때, 서비스 제공자(service provider)는 수임계정을 여럿 확보해, 서비스 이용자들을 위임계정으로 지정해 일정한 스테미나를 미리 확보해둔다면, 서비스 이용자는 트랜잭션 수수료에 대한 큰 부담 없이 탈중앙화 어플리케이션(dapp)을 손쉽게 이용 할 수 있다. 이는 PoC1에서 제시하는 모델이 전통적인 이더리움의 실행모델보다 훨씬 사용성을 개선한 것이라고 판단된다. 2.2.5 기존 이더리움 실행모델과 비교에서 보았듯이 이 모델은 스태미나 사용을 위해 트랜잭션 한번에 세 번의 이더리움 가상머신(evm)이 실행되는 구조로 되어 있어서 이더리움 메인넷에 적용을 하면 속도를 더 느리게 할 것이다. 그렇기 때문에 메인넷보다는 프라이빗 체인이나 사이드 체인에서 활용하는 것이 적합하다고 판단된다. 이러한 문제를 해결하기 위해PoC2 : Plasma EVM(https://hackmd.io/s/HyZ2ms8EX)에서는 PoC1에서 제안된 아키텍쳐를 메인넷에 연결된 사이드체인으로 활용할 수 있게 하는 방안에 대해 논의가 이뤄질 예정이다. # 참고자료 [1] [ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER BYZANTIUM VERSION e94ebda - 2018-06-05](https://ethereum.github.io/yellowpaper/paper.pdf)