revm icon indicating copy to clipboard operation
revm copied to clipboard

Ethereum tests failures when upgrading from revm 9 to 14

Open ufoscout opened this issue 1 year ago • 3 comments

When upgrading our project from revm 9 to 14, the following tests from the Ethereum suite fail with invalid state root:

ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stRevertTest/RevertPrefoundEmptyCallOOG.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stRevertTest/RevertPrefoundEmptyOOG.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stRevertTest/TouchToEmptyAccountRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stSStoreTest/InitCollision.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stStaticCall/static_call_OOG_additionalGasCosts2.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_CALLCODE_ToEmpty_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_CALLCODE_ToOneStorageKey_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_CALL_ToEmpty_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_CALL_ToOneStorageKey_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_DELEGATECALL_ToEmpty_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_DELEGATECALL_ToOneStorageKey_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_SUICIDE_ToEmpty_OOGRevert.json
ethereum_tests/12.2/BlockchainTests/GeneralStateTests/stZeroCallsRevert/ZeroValue_SUICIDE_ToOneStorageKey_OOGRevert.json

This is the function that executes the transactions:

pub(crate) fn transact_commit_with_inspect<DB>(
    env: Env,
    db: DB,
) -> ExecutionResult
where
    DB: Database + DatabaseCommit,
{
    let mut evm = revm::Evm::builder()
        .with_db(db)
        .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_spec_id(
            env.into(),
            FORK_SPEC_ID, // Paris fork
        ))
        .append_handler_register(revm::inspector_handle_register)
        .build();

    evm.transact_commit().unwrap() // error handling omitted
}

revm dependency is declared as:

# Using "9.0" works as expected
revm = { version = "14.0", default-features = false, features = [
    "optional_eip3607",
    "serde",
] }

Would you happen to have any clue? Is there anything we should look into? It seems that all failing tests are somehow related to a tx revert

ufoscout avatar Sep 25 '24 14:09 ufoscout

could be a feature thing, blst, c-kzg

mattsse avatar Sep 25 '24 14:09 mattsse

Same issue with:

revm = { version = "14.0", default-features = true, features = [
    "serde",
    "arbitrary",
    "asm-keccak",
    "dev",
    "kzg-rs",
] }

ufoscout avatar Sep 25 '24 15:09 ufoscout

There is EIP (https://eips.ethereum.org/EIPS/eip-7610) that changed how an Account is considered empty, in essence, it requires storage to be empty.

In Revm we don't have that check as it is close to impossible to trigger on mainnet as there are only few empty accounts with storage and you would need to get a hash collision to trigger it.

Statetest are handmade with some storage, there is list in Revm here: https://github.com/bluealloy/revm/blob/e477c7fb578acd55d1f7a95b1a80392d8ede9d22/bins/revme/src/cmd/statetest/runner.rs#L115-L125

rakita avatar Sep 25 '24 16:09 rakita

@rakita We have investigated more and we can reproduce the error with every REVM version >= 10.

Even by ignoring the InitialCollision.json test as shown in your code, there are still 12 other tests that fail with revm 10 and work as expected with revm 9:

GeneralStateTests/stRevertTest/RevertPrefoundEmptyCallOOG.json
GeneralStateTests/stRevertTest/RevertPrefoundEmptyOOG.json
GeneralStateTests/stRevertTest/TouchToEmptyAccountRevert.json
GeneralStateTests/stSStoreTest/InitCollision.json
GeneralStateTests/stStaticCall/static_call_OOG_additionalGasCosts2.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_CALLCODE_ToEmpty_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_CALLCODE_ToOneStorageKey_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_CALL_ToEmpty_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_CALL_ToOneStorageKey_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_DELEGATECALL_ToEmpty_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_DELEGATECALL_ToOneStorageKey_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_SUICIDE_ToEmpty_OOGRevert.json
GeneralStateTests/stZeroCallsRevert/ZeroValue_SUICIDE_ToOneStorageKey_OOGRevert.json

Any idea?

ufoscout avatar Nov 19 '24 09:11 ufoscout

@rakita Our tests are broken by this commit: https://github.com/bluealloy/revm/commit/9955d9faa5ca4cefc30bde3eb37ba11af7ef474d

After that commit the DatabaseCommit::commit fn is called with a different set of changes. For example, in the case of the BlockchainTests/GeneralStateTests/stRevertTest/RevertPrefoundEmptyOOG.json test, the set of changes before the commit is:

{
    0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba: Account {
        info: AccountInfo {
            balance: 0,
            nonce: 0,
            code_hash: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
            code: None,
        },
        storage: {},
        status: AccountStatus(
            Touched,
        ),
    },
    0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b: Account {
        info: AccountInfo {
            balance: 999990700000,
            nonce: 1,
            code_hash: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
            code: None,
        },
        storage: {},
        status: AccountStatus(
            Touched,
        ),
    },
    0xa000000000000000000000000000000000000000: Account {
        info: AccountInfo {
            balance: 1,
            nonce: 0,
            code_hash: 0x7b5b62ce77e883bfb4a35d856b28fe6d3d38b2d689920b7f6ff96a1d8df3cbea,
            code: Some(
                LegacyRaw(
                    0x602060006000f0600055622fffff60002000,
                ),
            ),
        },
        storage: {},
        status: AccountStatus(
            0x0,
        ),
    },
}

but after that commit the changes are:

{
    0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b: Account {
        info: AccountInfo {
            balance: 999990700000,
            nonce: 1,
            code_hash: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
            code: None,
        },
        storage: {},
        status: AccountStatus(
            Touched,
        ),
    },
    0xa000000000000000000000000000000000000000: Account {
        info: AccountInfo {
            balance: 1,
            nonce: 0,
            code_hash: 0x7b5b62ce77e883bfb4a35d856b28fe6d3d38b2d689920b7f6ff96a1d8df3cbea,
            code: Some(
                LegacyRaw(
                    0x602060006000f0600055622fffff60002000,
                ),
            ),
        },
        storage: {
            0: EvmStorageSlot {
                original_value: 0,
                present_value: 0,
                is_cold: true,
            },
        },
        status: AccountStatus(
            0x0,
        ),
    },
    0x7db299e0885c85039f56fa504a13dd8ce8a56aa7: Account {
        info: AccountInfo {
            balance: 0,
            nonce: 0,
            code_hash: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
            code: None,
        },
        storage: {},
        status: AccountStatus(
            Cold,
        ),
    },
    0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba: Account {
        info: AccountInfo {
            balance: 0,
            nonce: 0,
            code_hash: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
            code: None,
        },
        storage: {},
        status: AccountStatus(
            Touched,
        ),
    },
}

Should we ignore everything marked as cold?

ufoscout avatar Nov 22 '24 15:11 ufoscout

Should we ignore everything marked as cold?

This is the solution. It is more performant if I don't remove the item from the hashmap, and it is helpful (a nice feature) to see all loaded accounts.

For info, I am doing a lot of refactoring in the current main branch (PR https://github.com/bluealloy/revm/pull/1865). I am not sure about your timeline, but first version of this change should be made in a next few weeks. This is part of making the Revm an Evm Framework.

rakita avatar Nov 22 '24 15:11 rakita

Should we ignore everything marked as cold?

This is the solution.

Great! Should we also ignore storage slots with is_cold: true?

ufoscout avatar Nov 22 '24 15:11 ufoscout

Great! Should we also ignore storage slots with is_cold: true?

Delayed answer, but yes. if is_cold is found it means storage or account reverted its access.

rakita avatar Mar 13 '25 17:03 rakita