Skip to content

Commit 5fbc033

Browse files
committed
Add tests for flashblock state visibility
Add two tests that verify pending state from flashblocks is correctly visible: - test_eth_call_sees_flashblock_state_changes: Verifies eth_call to pending block sees balance changes from executed flashblock transactions - test_sequential_nonces_across_flashblocks: Verifies transactions in flashblock N+1 can see state changes (nonces) from flashblock N
1 parent 1942db8 commit 5fbc033

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

crates/flashblocks-rpc/tests/state.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,146 @@ async fn test_duplicate_flashblock_ignored() {
838838
assert_eq!(block, block_two);
839839
}
840840

841+
/// Verifies that eth_call targeting pending block sees flashblock state changes.
842+
///
843+
/// This test catches database layering bugs where pending state from flashblocks
844+
/// isn't visible to RPC callers. After a flashblock transfers ETH to Bob, an
845+
/// eth_call simulating a transfer FROM Bob should succeed because Bob now has
846+
/// more funds from the flashblock.
847+
#[tokio::test]
848+
async fn test_eth_call_sees_flashblock_state_changes() {
849+
use alloy_eips::BlockNumberOrTag;
850+
use alloy_provider::Provider;
851+
use alloy_rpc_types_eth::TransactionInput;
852+
use op_alloy_rpc_types::OpTransactionRequest;
853+
854+
let test = TestHarness::new().await;
855+
let provider = test.node.provider();
856+
857+
let bob_address = test.address(User::Bob);
858+
let charlie_address = test.address(User::Charlie);
859+
860+
// Get Bob's canonical balance to calculate a transfer amount that exceeds it
861+
let canonical_balance = provider.get_balance(bob_address).await.unwrap();
862+
863+
// Send base flashblock
864+
test.send_flashblock(FlashblockBuilder::new_base(&test).build()).await;
865+
866+
// Flashblock 1: Alice sends a large amount to Bob
867+
let transfer_to_bob = 1_000_000_000_000_000_000u128; // 1 ETH
868+
let tx = test.build_transaction_to_send_eth_with_nonce(
869+
User::Alice,
870+
User::Bob,
871+
transfer_to_bob,
872+
0,
873+
);
874+
test.send_flashblock(
875+
FlashblockBuilder::new(&test, 1)
876+
.with_transactions(vec![tx])
877+
.build(),
878+
)
879+
.await;
880+
881+
// Verify via state overrides that Bob received the funds
882+
let overrides = test
883+
.flashblocks
884+
.get_pending_blocks()
885+
.get_state_overrides()
886+
.expect("state overrides should exist after flashblock execution");
887+
let bob_override = overrides.get(&bob_address).expect("Bob should have a state override");
888+
let bob_pending_balance = bob_override.balance.expect("Bob's balance override should be set");
889+
assert_eq!(
890+
bob_pending_balance,
891+
canonical_balance + U256::from(transfer_to_bob),
892+
"State override should show Bob's increased balance"
893+
);
894+
895+
// Now the key test: eth_call from Bob should see this pending balance.
896+
// Try to transfer more than Bob's canonical balance (but less than pending).
897+
// This would fail if eth_call can't see the pending state.
898+
let transfer_amount = canonical_balance + U256::from(100_000u64);
899+
let call_request = OpTransactionRequest::default()
900+
.from(bob_address)
901+
.to(charlie_address)
902+
.value(transfer_amount)
903+
.gas_limit(21_000)
904+
.input(TransactionInput::default());
905+
906+
let result = provider.call(call_request).block(BlockNumberOrTag::Pending.into()).await;
907+
assert!(
908+
result.is_ok(),
909+
"eth_call from Bob should succeed because pending state shows increased balance. \
910+
If this fails, eth_call may not be seeing flashblock state changes. Error: {:?}",
911+
result.err()
912+
);
913+
}
914+
915+
/// Verifies that transactions in flashblock N+1 can see state changes from flashblock N.
916+
///
917+
/// This test catches database layering bugs where writes from earlier flashblocks
918+
/// aren't visible to later flashblock execution. The key is that flashblock 2's
919+
/// transaction uses nonce=1, which only succeeds if the execution layer sees
920+
/// flashblock 1's transaction (which used nonce=0).
921+
#[tokio::test]
922+
async fn test_sequential_nonces_across_flashblocks() {
923+
let test = TestHarness::new().await;
924+
925+
// Send base flashblock
926+
test.send_flashblock(FlashblockBuilder::new_base(&test).build()).await;
927+
928+
// Flashblock 1: Alice sends to Bob with nonce 0
929+
let tx_nonce_0 = test.build_transaction_to_send_eth_with_nonce(User::Alice, User::Bob, 1000, 0);
930+
test.send_flashblock(
931+
FlashblockBuilder::new(&test, 1)
932+
.with_transactions(vec![tx_nonce_0])
933+
.build(),
934+
)
935+
.await;
936+
937+
// Verify flashblock 1 was processed - Alice's pending nonce should now be 1
938+
let alice_state = test.account_state(User::Alice);
939+
assert_eq!(
940+
alice_state.nonce, 1,
941+
"After flashblock 1, Alice's pending nonce should be 1"
942+
);
943+
944+
// Flashblock 2: Alice sends to Charlie with nonce 1
945+
// This will FAIL if the execution layer can't see flashblock 1's state change
946+
let tx_nonce_1 =
947+
test.build_transaction_to_send_eth_with_nonce(User::Alice, User::Charlie, 2000, 1);
948+
test.send_flashblock(
949+
FlashblockBuilder::new(&test, 2)
950+
.with_transactions(vec![tx_nonce_1])
951+
.build(),
952+
)
953+
.await;
954+
955+
// Verify flashblock 2 was processed - Alice's pending nonce should now be 2
956+
let alice_state_after = test.account_state(User::Alice);
957+
assert_eq!(
958+
alice_state_after.nonce, 2,
959+
"After flashblock 2, Alice's pending nonce should be 2. \
960+
If this fails, the database layering may be preventing flashblock 2 \
961+
from seeing flashblock 1's state changes."
962+
);
963+
964+
// Also verify Bob and Charlie received their funds
965+
let overrides = test
966+
.flashblocks
967+
.get_pending_blocks()
968+
.get_state_overrides()
969+
.expect("state overrides should exist");
970+
971+
assert!(
972+
overrides.get(&test.address(User::Bob)).is_some(),
973+
"Bob should have received funds from flashblock 1"
974+
);
975+
assert!(
976+
overrides.get(&test.address(User::Charlie)).is_some(),
977+
"Charlie should have received funds from flashblock 2"
978+
);
979+
}
980+
841981
#[tokio::test]
842982
async fn test_progress_canonical_blocks_without_flashblocks() {
843983
let mut test = TestHarness::new().await;

0 commit comments

Comments
 (0)