The hard fork during block 1920000 contained the execution of an irregular state change. For reference purposes it would be great to have a technical overview of these changes and how they were enforced.



The Ethereum node client software was updated to make the following changes to hard-fork the block chain in block #1,920,000 by applying the following additional block processing rules:

  • Mark block #1,920,000 with the extra data dao-hard-fork (Hex:0x64616f2d686172642d666f726b).
  • Add the ether balance for all DAO accounts to the refund contract account, and zero out the balance for all these DAO accounts.

A total of 12,001,961.845205763407115004 ETH was swept from 58 The DAO and child DAO contracts into the refund contract at the hard-fork block #1,920,000.

There are no transactions in block #1,920,000 reflecting these balance changes, but these balance changes can be traced by reading the source code for the Ethereum node client software.


Here is the block number in github.com/ethereum/go-ethereum/params/dao.go#L34:

var MainNetDAOForkBlock = big.NewInt(1920000)

There is an extra data marker in github.com/ethereum/go-ethereum/params/dao.go:

var DAOForkBlockExtra = common.FromHex("0x64616f2d686172642d666f726b")

You can see the DAO hard-fork extra data in #1,920,000: enter image description here

And here is the refund contract address in github.com/ethereum/go-ethereum/params/dao.go#L46:

var DAORefundContract = common.HexToAddress("0xbf4ed7b27f1d666546e30d74d50d173d20bca754")

And there is a list of 58 accounts to drain (1, 2, ...) in github.com/ethereum/go-ethereum/params/dao.go#L67-L417:

const daoDrainListJSON = `

Here is where the fork data changes are applied to block #1,920,000 in github.com/ethereum/go-ethereum/core/chain_makers.go#L190-L200 - the first section applies the extra data, and the second section calls the function to perform the forking magic:

func GenerateChain(config *ChainConfig, parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
if daoBlock := config.DAOForkBlock; daoBlock != nil {
    limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
    if h.Number.Cmp(daoBlock) >= 0 && h.Number.Cmp(limit) < 0 {
        if config.DAOForkSupport {
            h.Extra = common.CopyBytes(params.DAOForkBlockExtra)
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(h.Number) == 0 {

And finally, here is the function that performs the forking magic in github.com/ethereum/go-ethereum/core/dao.go#L63-L74 - the code moves the balance from each DAO account to the refund address and zeroes out the balance for those DAO accounts:

func ApplyDAOHardFork(statedb *state.StateDB) {
    // Retrieve the contract to refund balances into
    refund := statedb.GetOrNewStateObject(params.DAORefundContract)

    // Move every DAO account and extra-balance account funds into the refund contract
    for _, addr := range params.DAODrainList {
        if account := statedb.GetStateObject(addr); account != nil {

Here is the same type of code to set the extra data and transfer the balances in the Parity client from github.com/ethcore/parity/ethcore/src/ethereum/ethash.rs#L131-L150:

    if header.number >= self.ethash_params.dao_hardfork_transition &&
            header.number <= self.ethash_params.dao_hardfork_transition + 9 {
            header.extra_data = b"dao-hard-fork"[..].to_owned();
//      info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number, header.difficulty, header.gas_limit);

    fn on_new_block(&self, block: &mut ExecutedBlock) {
        if block.fields().header.number == self.ethash_params.dao_hardfork_transition {
            // TODO: enable trigger function maybe?
//          if block.fields().header.gas_limit <= 4_000_000.into() {
                let mut state = block.fields_mut().state;
                for child in &self.ethash_params.dao_hardfork_accounts {
                    let b = state.balance(child);
                    state.transfer_balance(child, &self.ethash_params.dao_hardfork_beneficiary, &b);
//          }

A total of 12,001,961.845205763407115004 ETH was swept from the DAO contracts into the refund contract at the hard-fork block #1,920,000.

Following is the change in the balance from block #1,919,999 to block #1,920,000 in the refund contract:

> web3.fromWei(eth.getBalance("0xbf4ed7b27f1d666546e30d74d50d173d20bca754",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0xbf4ed7b27f1d666546e30d74d50d173d20bca754",1920000),"ether")

And following is the change in the balance from block #1,919,999 to block #1,920,000 in the The DAO and a few of the child DAOs:

// The DAO
> web3.fromWei(eth.getBalance("0xbb9bc244d798123fde783fcc1c72d3bb8c189413",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0xbb9bc244d798123fde783fcc1c72d3bb8c189413",1920000),"ether")
> web3.fromWei(eth.getBalance("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728",1920000),"ether")
> web3.fromWei(eth.getBalance("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f",1920000),"ether")
// Baddies DAO
> web3.fromWei(eth.getBalance("0x304a554a310c7e546dfe434669c62820b7d83490",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0x304a554a310c7e546dfe434669c62820b7d83490",1920000),"ether")
// Goodies DAOs
> web3.fromWei(eth.getBalance("0xb136707642a4ea12fb4bae820f03d2562ebff487",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0xb136707642a4ea12fb4bae820f03d2562ebff487",1920000),"ether")
> web3.fromWei(eth.getBalance("0x84ef4b2357079cd7a7c69fd7a37cd0609a679106",1920000-1),"ether")
> web3.fromWei(eth.getBalance("0x84ef4b2357079cd7a7c69fd7a37cd0609a679106",1920000),"ether")

Here are some github commits of interest:

  • So by "irregular," the node "blinks" during this block and accepts the balance state changes without checking for valid txn signatures. Is that right?
    – GViz
    Sep 6 '19 at 18:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.