|
| 1 | +# EIP-779: Hardfork Meta: DAO Fork |
| 2 | + |
| 3 | + |
| 4 | +## 요약 |
| 5 | +"`DAO Fork`"는 ***The DAO Hack*** 사건과 관련있는 *hard fork* 입니다. |
| 6 | +다른 *hard fork* 들과 달리, `DAO Fork` 는 EVM opcode, 트랜잭션 형식, 블록 구조 등의 `Protocol` 자체에는 수정 사항이 없습니다. |
| 7 | +대신에, `DAO Fork`는 계정 목록들("child DAO" contracts)로부터 특정 계정("WithdrawDAO" contract)으로 `ether` 잔액을 전송하는 *불규칙적인 상태 변화* 를 일으키는 *hard fork* 입니다. |
| 8 | + |
| 9 | +## EIP 제안 동기 |
| 10 | +EIP-779는 The DAO Hack 사건으로 인해 드러난 이더리움의 보안 취약점과 그로 인한 커뮤니티의 신뢰 손상을 해결하기 위해 제안되었습니다. |
| 11 | +수백만 달러 상당의 `Ether` 도난은 네트워크 안정성에 대한 우려를 촉발시켜, 커뮤니티는 효과적인 대응책을 강력히 요구했습니다. |
| 12 | +EIP-779는 이러한 문제를 해결하고자 하드 포크(*hard fork*)를 통해 도난 자금을 회수하는 등 구체적인 기술적 조치를 제안함으로써, 이더리움 네트워크의 복원력을 강화하는 데 중요한 역할을 했습니다. |
| 13 | + |
| 14 | +## 선정 이유 |
| 15 | +EVM Security 에 대해 공부할 때 흥미롭게 본 `Re-entrancy Attack`의 대표적인 사례가 바로 ***The DAO Hack*** 사건이었습니다. |
| 16 | +해킹 사건 발생 후에 어떤 조치가 어떻게 일어났는지 알아보고 싶어 선정하게 되었습니다. |
| 17 | +또, 평소 궁금하던 *hard fork* 적용에 대해서도 살펴보고 싶어 선정하게 되었습니다. |
| 18 | + |
| 19 | +## 본론 |
| 20 | + |
| 21 | +`geth` 클라이언트 프로그램에 구현된 `DAO Fork`에 관한 내용은 다음과 같습니다. |
| 22 | + |
| 23 | +**The DAO** 컨트랙트(`0xbb9bc244d798123fde783fcc1c72d3bb8c189413`), **extraBalance** 컨트랙트 (`0x807640a13483f8ac783c557fcdf27be11ea4ac7a`), |
| 24 | +모든 **The DAO Creator** 컨트랙트의 *자식* 계정들(`0x4a574510c7014e4ae985403536074abe582adfc8`), 각각의 *자식* 계정들에 대한 *extraBalance* 계정 등이 모두 |
| 25 | +$L$ 이라는 목록으로 인코딩되어 $1,880,000$ 번째 블록에 기록되었습니다. |
| 26 | + |
| 27 | +계정 목록 $L$ 은 [gist file](https://gist.github.com/gavofyork/af747a034fbee2920f862ed352d32347)에서 확인할 수 있습니다. |
| 28 | + |
| 29 | +$1,920,000$ 번째 블록의 시작 이후에 모든 `Ether` 잔액은 $L$ 로부터 특정 계정 $C$ (`0xbf4ed7b27f1d666546e30d74d50d173d20bca754`) 로 전송됩니다. |
| 30 | +중요한 점은 트랜잭션을 통한 송금이 아니라 프로토콜 단에서 강제로 `Ether` 를 옮긴다는 것입니다. |
| 31 | + |
| 32 | +계정 $C$ 는 `WithdrawDAO` 라는 이름의 스마트 컨트랙트가 [구현](https://etherscan.io/address/0xbf4ed7b27f1d666546e30d74d50d173d20bca754#code)된 계정입니다. |
| 33 | + |
| 34 | +```solidity |
| 35 | +/** |
| 36 | + *Submitted for verification at Etherscan.io on 2016-07-14 |
| 37 | +*/ |
| 38 | +
|
| 39 | +contract DAO { |
| 40 | + function balanceOf(address addr) returns (uint); |
| 41 | + function transferFrom(address from, address to, uint balance) returns (bool); |
| 42 | + uint public totalSupply; |
| 43 | +} |
| 44 | +
|
| 45 | +contract WithdrawDAO { |
| 46 | + DAO constant public mainDAO = DAO(0xbb9bc244d798123fde783fcc1c72d3bb8c189413); |
| 47 | + address public trustee = 0xda4a4626d3e16e094de3225a751aab7128e96526; |
| 48 | +
|
| 49 | + function withdraw(){ |
| 50 | + uint balance = mainDAO.balanceOf(msg.sender); |
| 51 | +
|
| 52 | + if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance)) |
| 53 | + throw; |
| 54 | + } |
| 55 | +
|
| 56 | + function trusteeWithdraw() { |
| 57 | + trustee.send((this.balance + mainDAO.balanceOf(this)) - mainDAO.totalSupply()); |
| 58 | + } |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +이 중 `withdraw()` 함수를 자세히 살펴보겠습니다. |
| 63 | + |
| 64 | +```solidity |
| 65 | +function withdraw(){ |
| 66 | + uint balance = mainDAO.balanceOf(msg.sender); |
| 67 | +
|
| 68 | + if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance)) |
| 69 | + throw; |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +우선은 `mainDAO` 컨트랙트에 존재하는 토큰의 수를 `balance` 변수에 저장합니다. |
| 74 | +이후, if 절에서 ***토큰 이전 시도*** 와 ***이더 전송 시도*** 를 진행합니다. |
| 75 | +만일 둘 중 하나라도 정상적으로 이뤄지지 않는다면, `throw`를 통해 예외 처리를 합니다. |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +### Geth |
| 80 | + |
| 81 | +실제 `geth` 코드 내에서는 다음과 같은 코드들을 확인할 수 있었습니다. |
| 82 | + |
| 83 | +#### `params/dao.go` |
| 84 | +- `DAO Fork`를 적용하기 위한 여러 *parameter* 값들을 기록한 파일입니다. |
| 85 | +- 사고로 흩어진 `ether`를 모으기 위해 작성된 컨트랙트 ***DAORefundContract***, |
| 86 | +해커로 인해 `ether`가 비정상적으로 모이게 된 계정들의 목록 ***DrainList*** 등을 명시하고 있습니다. |
| 87 | +```go |
| 88 | +// DAOForkBlockExtra is the block header extra-data field to set for the DAO fork |
| 89 | +// point and a number of consecutive blocks to allow fast/light syncers to correctly |
| 90 | +// pick the side they want ("dao-hard-fork"). |
| 91 | +// EIP-779, DAO hard-fork 지점 이후 및 추가로 연속되는 블록들의 `extra-data` 필드에 |
| 92 | +// "dao-hard-fork" 를 16진수 형태로 변환하여 기록합니다. |
| 93 | +// 이를 통해, 빠른 동기화나 경량 클라이언트 같은 동기화 메커니즘들이 올바른 체인을 선택하도록 돕습니다. |
| 94 | +var DAOForkBlockExtra = common.FromHex("0x64616f2d686172642d666f726b") |
| 95 | + |
| 96 | +// DAOForkExtraRange is the number of consecutive blocks from the DAO fork point |
| 97 | +// to override the extra-data in to prevent no-fork attacks. |
| 98 | +// EIP-779, `no-fork attack` 으로부터 체인을 보호하기 위해 얼마나 많은 |
| 99 | +// DAO fork 지점 이후 연속되는 블록의 `extra-data`에 덮어쓰기를 할 것인지 명시합니다. |
| 100 | +var DAOForkExtraRange = big.NewInt(10) |
| 101 | + |
| 102 | +// DAORefundContract is the address of the refund contract to send DAO balances to. |
| 103 | +// EIP-779, 환불(refund)을 위해 사용할 refund contract 의 주소를 명시합니다. |
| 104 | +var DAORefundContract = common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754") |
| 105 | + |
| 106 | +// DAODrainList is the list of accounts whose full balances will be moved into a |
| 107 | +// refund contract at the beginning of the dao-fork block. |
| 108 | +// EIP-779, 돈을 회수할 계정을 명시합니다. |
| 109 | +func DAODrainList() []common.Address { |
| 110 | + return []common.Address{ |
| 111 | + common.HexToAddress("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728"), |
| 112 | + common.HexToAddress("0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425"), |
| 113 | + common.HexToAddress("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f"), |
| 114 | + common.HexToAddress("0xecd135fa4f61a655311e86238c92adcd779555d2"), |
| 115 | + common.HexToAddress("0x1975bd06d486162d5dc297798dfc41edd5d160a7"), |
| 116 | + // ... 생략 |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +``` |
| 121 | + |
| 122 | +#### `consensus/misc/dao.go` |
| 123 | +- `params/dao.go` 에서의 값들을 바탕으로 `DAO Fork` 를 적용하는 함수가 정의된 파일입니다. |
| 124 | +- `DAORefundContract` 의 존재 여부를 검사하고, `DAODrainList` 에 명시된 계정들로부터 |
| 125 | +`DAORefundContract`로 `Ether` 를 강제로 옮기는 과정을 진행합니다. |
| 126 | +```go |
| 127 | +// ApplyDAOHardFork modifies the state database according to the DAO hard-fork |
| 128 | +// rules, transferring all balances of a set of DAO accounts to a single refund |
| 129 | +// contract. |
| 130 | +// |
| 131 | +// EIP-779, TheDAO hard-fork 에 따라 DB의 상태를 변경하는 함수입니다. |
| 132 | +// EIP-779에서도 설명하듯이 DAODrainList 의 계정들로부터 하나의 DAORefundContract 에 |
| 133 | +// 돈을 전송하게 됩니다. |
| 134 | +func ApplyDAOHardFork(statedb *state.StateDB) { |
| 135 | + // Retrieve the contract to refund balances into |
| 136 | + // EIP-779, 돈을 받을 계정이 존재하지 않는다면 새로 하나 생성합니다. |
| 137 | + // 참고로, 계정주소는 "common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754")" 입니다. |
| 138 | + if !statedb.Exist(params.DAORefundContract) { |
| 139 | + statedb.CreateAccount(params.DAORefundContract) |
| 140 | + } |
| 141 | + |
| 142 | + // Move every DAO account and extra-balance account funds into the refund contract |
| 143 | + // 모든 `DAODrainList` 의 계정으로부터 `refund contract`에 돈을 보냅니다. |
| 144 | + for _, addr := range params.DAODrainList() { |
| 145 | + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract) |
| 146 | + statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount) |
| 147 | + } |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +#### `core/chain_makers.go` |
| 152 | +- `chain_makers.go` 파일은 블록체인에 블록을 생성하고 추가하는 기능과 관련된 유틸리티와 도구들을 정의합니다. |
| 153 | +- `DAO fork` 와 같은 *hard fork* 를 적용하는 것도 아래 코드와 같이 구현되어 있습니다. |
| 154 | +```go |
| 155 | +// ... |
| 156 | + // EIP-779, DAO fork 를 지원하고 Chain Config에 DAO fork 블록 넘버가 현재 블록 넘버와 동일하다면 |
| 157 | + // TheDAO hard-fork 를 적용합니다. |
| 158 | + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { |
| 159 | + misc.ApplyDAOHardFork(statedb) |
| 160 | + } |
| 161 | +// ... |
| 162 | +``` |
| 163 | + |
| 164 | +#### `core/state_processor.go` |
| 165 | +- 이더리움의 핵심 기능 중 하나인 상태 전이(state transition) 을 구현합니다. |
| 166 | +- `DAO fork`에 관한 코드도 구현되어 있는 것을 확인할 수 있습니다. |
| 167 | +```go |
| 168 | +// Process processes the state changes according to the Ethereum rules by running |
| 169 | +// the transaction messages using the statedb and applying any rewards to both |
| 170 | +// the processor (coinbase) and any included uncles. |
| 171 | +// |
| 172 | +// Process returns the receipts and logs accumulated during the process and |
| 173 | +// returns the amount of gas that was used in the process. If any of the |
| 174 | +// transactions failed to execute due to insufficient gas it will return an error. |
| 175 | +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { |
| 176 | + var ( |
| 177 | + receipts types.Receipts |
| 178 | + usedGas = new(uint64) |
| 179 | + header = block.Header() |
| 180 | + blockHash = block.Hash() |
| 181 | + blockNumber = block.Number() |
| 182 | + allLogs []*types.Log |
| 183 | + gp = new(GasPool).AddGas(block.GasLimit()) |
| 184 | + ) |
| 185 | + |
| 186 | + // Mutate the block and state according to any hard-fork specs |
| 187 | + // EIP-779, DAO fork 를 지원하고 Chain Config에 DAO fork 블록 넘버가 현재 블록 넘버와 동일하다면 |
| 188 | + // TheDAO hard-fork 를 적용합니다. |
| 189 | + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { |
| 190 | + misc.ApplyDAOHardFork(statedb) |
| 191 | + } |
| 192 | +// ... |
| 193 | +``` |
| 194 | +
|
| 195 | +--- |
| 196 | +
|
| 197 | +**The DAO 해킹 사건**에서는 해커가 탈취한 이더가 28일의 잠금 기간을 갖는 자식 DAO 계정에 입금되었기 때문에, 투자자들이 즉각적으로 자금을 잃는 상황은 발생하지 않았습니다. |
| 198 | +이러한 잠금 기간은 이더리움 커뮤니티에 대응할 시간을 제공했고, 결국 `DAO hard fork` 를 통해 탈취된 자금의 대부분을 회수할 수 있었습니다. |
| 199 | +그러나 모든 커뮤니티 구성원이 이 *hard fork* 를 지지한 것은 아니었으며, *hard fork* 를 반대하는 일부는 `이더리움 클래식`(`Ethereum Classic`, `ETC`)이라는 새로운 체인을 만들어 `이더리움`의 원래 체인과 분리되었습니다. |
| 200 | +`이더리움 클래식`은 *hard fork* 를 반영하지 않고 **The DAO 해킹** 전의 원래 체인을 유지하고 있습니다. |
| 201 | +
|
| 202 | +
|
| 203 | +## Reference |
| 204 | +https://eips.ethereum.org/EIPS/eip-779 |
| 205 | +
|
| 206 | +https://medium.com/swlh/the-story-of-the-dao-its-history-and-consequences-71e6a8a551ee |
| 207 | +
|
| 208 | +https://ethereum.stackexchange.com/questions/3981/what-is-the-address-and-balance-of-the-daos-extrabalance-account |
| 209 | +
|
| 210 | +https://www.gemini.com/cryptopedia/the-dao-hack-makerdao#section-the-response-to-the-dao-hack |
0 commit comments