forked from ethereum/go-ethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |