diff --git a/EIPs/eip-779.md b/EIPs/eip-779.md new file mode 100644 index 000000000000..1ef24b192d63 --- /dev/null +++ b/EIPs/eip-779.md @@ -0,0 +1,210 @@ +# EIP-779: Hardfork Meta: DAO Fork + + +## 요약 +"`DAO Fork`"는 ***The DAO Hack*** 사건과 관련있는 *hard fork* 입니다. +다른 *hard fork* 들과 달리, `DAO Fork` 는 EVM opcode, 트랜잭션 형식, 블록 구조 등의 `Protocol` 자체에는 수정 사항이 없습니다. +대신에, `DAO Fork`는 계정 목록들("child DAO" contracts)로부터 특정 계정("WithdrawDAO" contract)으로 `ether` 잔액을 전송하는 *불규칙적인 상태 변화* 를 일으키는 *hard fork* 입니다. + +## EIP 제안 동기 +EIP-779는 The DAO Hack 사건으로 인해 드러난 이더리움의 보안 취약점과 그로 인한 커뮤니티의 신뢰 손상을 해결하기 위해 제안되었습니다. +수백만 달러 상당의 `Ether` 도난은 네트워크 안정성에 대한 우려를 촉발시켜, 커뮤니티는 효과적인 대응책을 강력히 요구했습니다. +EIP-779는 이러한 문제를 해결하고자 하드 포크(*hard fork*)를 통해 도난 자금을 회수하는 등 구체적인 기술적 조치를 제안함으로써, 이더리움 네트워크의 복원력을 강화하는 데 중요한 역할을 했습니다. + +## 선정 이유 +EVM Security 에 대해 공부할 때 흥미롭게 본 `Re-entrancy Attack`의 대표적인 사례가 바로 ***The DAO Hack*** 사건이었습니다. +해킹 사건 발생 후에 어떤 조치가 어떻게 일어났는지 알아보고 싶어 선정하게 되었습니다. +또, 평소 궁금하던 *hard fork* 적용에 대해서도 살펴보고 싶어 선정하게 되었습니다. + +## 본론 + +`geth` 클라이언트 프로그램에 구현된 `DAO Fork`에 관한 내용은 다음과 같습니다. + +**The DAO** 컨트랙트(`0xbb9bc244d798123fde783fcc1c72d3bb8c189413`), **extraBalance** 컨트랙트 (`0x807640a13483f8ac783c557fcdf27be11ea4ac7a`), +모든 **The DAO Creator** 컨트랙트의 *자식* 계정들(`0x4a574510c7014e4ae985403536074abe582adfc8`), 각각의 *자식* 계정들에 대한 *extraBalance* 계정 등이 모두 +$L$ 이라는 목록으로 인코딩되어 $1,880,000$ 번째 블록에 기록되었습니다. + +계정 목록 $L$ 은 [gist file](https://gist.github.com/gavofyork/af747a034fbee2920f862ed352d32347)에서 확인할 수 있습니다. + +$1,920,000$ 번째 블록의 시작 이후에 모든 `Ether` 잔액은 $L$ 로부터 특정 계정 $C$ (`0xbf4ed7b27f1d666546e30d74d50d173d20bca754`) 로 전송됩니다. +중요한 점은 트랜잭션을 통한 송금이 아니라 프로토콜 단에서 강제로 `Ether` 를 옮긴다는 것입니다. + +계정 $C$ 는 `WithdrawDAO` 라는 이름의 스마트 컨트랙트가 [구현](https://etherscan.io/address/0xbf4ed7b27f1d666546e30d74d50d173d20bca754#code)된 계정입니다. + +```solidity +/** + *Submitted for verification at Etherscan.io on 2016-07-14 +*/ + +contract DAO { + function balanceOf(address addr) returns (uint); + function transferFrom(address from, address to, uint balance) returns (bool); + uint public totalSupply; +} + +contract WithdrawDAO { + DAO constant public mainDAO = DAO(0xbb9bc244d798123fde783fcc1c72d3bb8c189413); + address public trustee = 0xda4a4626d3e16e094de3225a751aab7128e96526; + + function withdraw(){ + uint balance = mainDAO.balanceOf(msg.sender); + + if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance)) + throw; + } + + function trusteeWithdraw() { + trustee.send((this.balance + mainDAO.balanceOf(this)) - mainDAO.totalSupply()); + } +} +``` + +이 중 `withdraw()` 함수를 자세히 살펴보겠습니다. + +```solidity +function withdraw(){ + uint balance = mainDAO.balanceOf(msg.sender); + + if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance)) + throw; +} +``` + +우선은 `mainDAO` 컨트랙트에 존재하는 토큰의 수를 `balance` 변수에 저장합니다. +이후, if 절에서 ***토큰 이전 시도*** 와 ***이더 전송 시도*** 를 진행합니다. +만일 둘 중 하나라도 정상적으로 이뤄지지 않는다면, `throw`를 통해 예외 처리를 합니다. + +--- + +### Geth + +실제 `geth` 코드 내에서는 다음과 같은 코드들을 확인할 수 있었습니다. + +#### `params/dao.go` +- `DAO Fork`를 적용하기 위한 여러 *parameter* 값들을 기록한 파일입니다. +- 사고로 흩어진 `ether`를 모으기 위해 작성된 컨트랙트 ***DAORefundContract***, +해커로 인해 `ether`가 비정상적으로 모이게 된 계정들의 목록 ***DrainList*** 등을 명시하고 있습니다. +```go +// DAOForkBlockExtra is the block header extra-data field to set for the DAO fork +// point and a number of consecutive blocks to allow fast/light syncers to correctly +// pick the side they want ("dao-hard-fork"). +// EIP-779, DAO hard-fork 지점 이후 및 추가로 연속되는 블록들의 `extra-data` 필드에 +// "dao-hard-fork" 를 16진수 형태로 변환하여 기록합니다. +// 이를 통해, 빠른 동기화나 경량 클라이언트 같은 동기화 메커니즘들이 올바른 체인을 선택하도록 돕습니다. +var DAOForkBlockExtra = common.FromHex("0x64616f2d686172642d666f726b") + +// DAOForkExtraRange is the number of consecutive blocks from the DAO fork point +// to override the extra-data in to prevent no-fork attacks. +// EIP-779, `no-fork attack` 으로부터 체인을 보호하기 위해 얼마나 많은 +// DAO fork 지점 이후 연속되는 블록의 `extra-data`에 덮어쓰기를 할 것인지 명시합니다. +var DAOForkExtraRange = big.NewInt(10) + +// DAORefundContract is the address of the refund contract to send DAO balances to. +// EIP-779, 환불(refund)을 위해 사용할 refund contract 의 주소를 명시합니다. +var DAORefundContract = common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754") + +// DAODrainList is the list of accounts whose full balances will be moved into a +// refund contract at the beginning of the dao-fork block. +// EIP-779, 돈을 회수할 계정을 명시합니다. +func DAODrainList() []common.Address { + return []common.Address{ + common.HexToAddress("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728"), + common.HexToAddress("0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425"), + common.HexToAddress("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f"), + common.HexToAddress("0xecd135fa4f61a655311e86238c92adcd779555d2"), + common.HexToAddress("0x1975bd06d486162d5dc297798dfc41edd5d160a7"), + // ... 생략 + } +} + +``` + +#### `consensus/misc/dao.go` +- `params/dao.go` 에서의 값들을 바탕으로 `DAO Fork` 를 적용하는 함수가 정의된 파일입니다. +- `DAORefundContract` 의 존재 여부를 검사하고, `DAODrainList` 에 명시된 계정들로부터 +`DAORefundContract`로 `Ether` 를 강제로 옮기는 과정을 진행합니다. +```go +// ApplyDAOHardFork modifies the state database according to the DAO hard-fork +// rules, transferring all balances of a set of DAO accounts to a single refund +// contract. +// +// EIP-779, TheDAO hard-fork 에 따라 DB의 상태를 변경하는 함수입니다. +// EIP-779에서도 설명하듯이 DAODrainList 의 계정들로부터 하나의 DAORefundContract 에 +// 돈을 전송하게 됩니다. +func ApplyDAOHardFork(statedb *state.StateDB) { + // Retrieve the contract to refund balances into + // EIP-779, 돈을 받을 계정이 존재하지 않는다면 새로 하나 생성합니다. + // 참고로, 계정주소는 "common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754")" 입니다. + if !statedb.Exist(params.DAORefundContract) { + statedb.CreateAccount(params.DAORefundContract) + } + + // Move every DAO account and extra-balance account funds into the refund contract + // 모든 `DAODrainList` 의 계정으로부터 `refund contract`에 돈을 보냅니다. + for _, addr := range params.DAODrainList() { + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract) + statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount) + } +} +``` + +#### `core/chain_makers.go` +- `chain_makers.go` 파일은 블록체인에 블록을 생성하고 추가하는 기능과 관련된 유틸리티와 도구들을 정의합니다. +- `DAO fork` 와 같은 *hard fork* 를 적용하는 것도 아래 코드와 같이 구현되어 있습니다. +```go +// ... + // EIP-779, DAO fork 를 지원하고 Chain Config에 DAO fork 블록 넘버가 현재 블록 넘버와 동일하다면 + // TheDAO hard-fork 를 적용합니다. + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { + misc.ApplyDAOHardFork(statedb) + } +// ... +``` + +#### `core/state_processor.go` +- 이더리움의 핵심 기능 중 하나인 상태 전이(state transition) 을 구현합니다. +- `DAO fork`에 관한 코드도 구현되어 있는 것을 확인할 수 있습니다. +```go +// Process processes the state changes according to the Ethereum rules by running +// the transaction messages using the statedb and applying any rewards to both +// the processor (coinbase) and any included uncles. +// +// Process returns the receipts and logs accumulated during the process and +// returns the amount of gas that was used in the process. If any of the +// transactions failed to execute due to insufficient gas it will return an error. +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) + ) + + // Mutate the block and state according to any hard-fork specs + // EIP-779, DAO fork 를 지원하고 Chain Config에 DAO fork 블록 넘버가 현재 블록 넘버와 동일하다면 + // TheDAO hard-fork 를 적용합니다. + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } +// ... +``` + +--- + +**The DAO 해킹 사건**에서는 해커가 탈취한 이더가 28일의 잠금 기간을 갖는 자식 DAO 계정에 입금되었기 때문에, 투자자들이 즉각적으로 자금을 잃는 상황은 발생하지 않았습니다. +이러한 잠금 기간은 이더리움 커뮤니티에 대응할 시간을 제공했고, 결국 `DAO hard fork` 를 통해 탈취된 자금의 대부분을 회수할 수 있었습니다. +그러나 모든 커뮤니티 구성원이 이 *hard fork* 를 지지한 것은 아니었으며, *hard fork* 를 반대하는 일부는 `이더리움 클래식`(`Ethereum Classic`, `ETC`)이라는 새로운 체인을 만들어 `이더리움`의 원래 체인과 분리되었습니다. +`이더리움 클래식`은 *hard fork* 를 반영하지 않고 **The DAO 해킹** 전의 원래 체인을 유지하고 있습니다. + + +## Reference +https://eips.ethereum.org/EIPS/eip-779 + +https://medium.com/swlh/the-story-of-the-dao-its-history-and-consequences-71e6a8a551ee + +https://ethereum.stackexchange.com/questions/3981/what-is-the-address-and-balance-of-the-daos-extrabalance-account + +https://www.gemini.com/cryptopedia/the-dao-hack-makerdao#section-the-response-to-the-dao-hack