diff --git a/crates/optimism/tests/proofs/reorg/reorg_test.go b/crates/optimism/tests/proofs/reorg/reorg_test.go index 7df16d63064..69e1e0718bd 100644 --- a/crates/optimism/tests/proofs/reorg/reorg_test.go +++ b/crates/optimism/tests/proofs/reorg/reorg_test.go @@ -46,6 +46,19 @@ func TestReorgUsingAccountProof(gt *testing.T) { slots []common.Hash } var cases []caseEntry + + // deploy another contract in the reorged blocks + { + rContract, rDeployBlock := utils.DeploySimpleStorage(t, user) + t.Logf("Reorg SimpleStorage deployed at %s block=%d", rContract.Address().Hex(), rDeployBlock.BlockNumber.Uint64()) + + cases = append(cases, caseEntry{ + Block: rDeployBlock.BlockNumber.Uint64(), + addr: rContract.Address(), + slots: []common.Hash{common.HexToHash("0x0")}, + }) + } + for i := 0; i < 3; i++ { tx := alice.Transfer(bob.Address(), eth.OneGWei) receipt, err := tx.Included.Eval(ctx) @@ -74,18 +87,6 @@ func TestReorgUsingAccountProof(gt *testing.T) { }) } - // deploy another contract in the reorged blocks - { - rContract, rDeployBlock := utils.DeploySimpleStorage(t, user) - t.Logf("Reorg SimpleStorage deployed at %s block=%d", rContract.Address().Hex(), rDeployBlock.BlockNumber.Uint64()) - - cases = append(cases, caseEntry{ - Block: rDeployBlock.BlockNumber.Uint64(), - addr: rContract.Address(), - slots: []common.Hash{common.HexToHash("0x0")}, - }) - } - sys.L2CLSequencer.StopSequencer() var divergenceBlockNumber uint64 diff --git a/crates/optimism/tests/proofs/utils/proof.go b/crates/optimism/tests/proofs/utils/proof.go index b0199d5b293..89bf42f2b38 100644 --- a/crates/optimism/tests/proofs/utils/proof.go +++ b/crates/optimism/tests/proofs/utils/proof.go @@ -32,6 +32,20 @@ func NormalizeProofResponse(res *eth.AccountResult) { res.StorageProof[i].Proof = []hexutil.Bytes{} } } + + // Normalize empty CodeHash + // Geth returns 0x0000000000000000000000000000000000000000000000000000000000000000 + // Reth returns 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 + if res.CodeHash == (common.Hash{}) { + res.CodeHash = crypto.Keccak256Hash(nil) + } + + // Normalize empty StorageHash + // Geth returns 0x0000000000000000000000000000000000000000000000000000000000000000 + // Reth returns 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 + if res.StorageHash == (common.Hash{}) { + res.StorageHash = types.EmptyRootHash + } } // VerifyProof verifies an account and its storage proofs against a given state root. @@ -95,6 +109,18 @@ func VerifyProof(res *eth.AccountResult, stateRoot common.Hash) error { return fmt.Errorf("failed to verify account value with key %s (path %x) in account trie %s: %w", res.Address, path, stateRoot, err) } + // If the proof demonstrates non-existence (nil value), we must check if the RPC claimed the account is empty. + if len(accountProofValue) == 0 { + isEmpty := res.Nonce == 0 && + res.Balance.ToInt().Sign() == 0 && + (res.StorageHash == types.EmptyRootHash || res.StorageHash == common.Hash{}) && + (res.CodeHash == crypto.Keccak256Hash(nil) || res.CodeHash == common.Hash{}) + + if isEmpty { + return nil + } + } + if !bytes.Equal(accountClaimedValue, accountProofValue) { return fmt.Errorf("L1 RPC is tricking us, account proof does not match provided deserialized values:\n"+ " claimed: %x\n"+