diff --git a/src/pages/ethereum/execution/gas-profiler/CallPage.tsx b/src/pages/ethereum/execution/gas-profiler/CallPage.tsx index 5b64b9c44..7743baef5 100644 --- a/src/pages/ethereum/execution/gas-profiler/CallPage.tsx +++ b/src/pages/ethereum/execution/gas-profiler/CallPage.tsx @@ -86,21 +86,24 @@ export function CallPage(): JSX.Element { const [copied, setCopied] = useState(false); // Fetch all transaction data - // Pass blockNumber from URL search params for efficient partition-based filtering + // Requires blockFromSearch to match ClickHouse primary key prefix for efficient queries const { data: txData, isLoading, error, } = useTransactionGasData({ - transactionHash: txHash, + transactionHash: blockFromSearch ? txHash : null, blockNumber: blockFromSearch, }); + // Resolve block number: prefer API response (source of truth), fall back to URL param + const blockNumber = txData?.metadata.blockNumber ?? blockFromSearch ?? null; + // Fetch all opcode data for badge stats on call trace + // Requires blockNumber to match ClickHouse primary key prefix for efficient queries const { data: opcodeMap } = useAllCallFrameOpcodes({ - transactionHash: txHash, - blockNumber: blockFromSearch, - enabled: true, + transactionHash: blockFromSearch ? txHash : null, + blockNumber, }); // Derive badge stats from full opcode data (SSTORE, SLOAD, LOG*, CREATE*, SELFDESTRUCT) @@ -158,9 +161,6 @@ export function CallPage(): JSX.Element { return statsMap; }, [opcodeMap]); - // Block number: prefer from response (source of truth), fall back to URL param - const blockNumber = txData?.metadata.blockNumber ?? blockFromSearch ?? null; - // Fetch frame-specific opcodes (only when we have blockNumber) const { data: frameOpcodes, isLoading: frameOpcodesLoading } = useFrameOpcodes({ blockNumber, @@ -312,6 +312,29 @@ export function CallPage(): JSX.Element { return () => window.removeEventListener('popstate', handlePopState); }, [getTabIndexFromHash]); + // Missing block context (direct URL access without ?block= param) + if (!blockFromSearch) { + return ( + +
+ +
+ + + Back to Gas Profiler + +
+ + ); + } + // Loading state if (isLoading) { return ( diff --git a/src/pages/ethereum/execution/gas-profiler/TransactionPage.tsx b/src/pages/ethereum/execution/gas-profiler/TransactionPage.tsx index 2fe955328..2e286d8a9 100644 --- a/src/pages/ethereum/execution/gas-profiler/TransactionPage.tsx +++ b/src/pages/ethereum/execution/gas-profiler/TransactionPage.tsx @@ -86,22 +86,25 @@ export function TransactionPage(): JSX.Element { } | null>(null); // Fetch transaction data by tx hash - // Pass blockNumber from URL search params for efficient partition-based filtering + // Requires blockFromSearch to match ClickHouse primary key prefix for efficient queries const { data: txData, isLoading, error, } = useTransactionGasData({ - transactionHash: txHash, + transactionHash: blockFromSearch ? txHash : null, blockNumber: blockFromSearch, }); + // Resolve block number: prefer API response (source of truth), fall back to URL param + const blockNumber = txData?.metadata.blockNumber ?? blockFromSearch ?? null; + // Fetch all opcode data - used for both flame graph visualization AND badge stats // Single fetch instead of separate calls for badges vs flame graph + // Requires blockNumber to match ClickHouse primary key prefix for efficient queries const { data: opcodeMap, isLoading: isLoadingOpcodes } = useAllCallFrameOpcodes({ - transactionHash: txHash, - blockNumber: blockFromSearch, - enabled: true, // Always fetch - eliminates duplicate API call for badge data + transactionHash: blockFromSearch ? txHash : null, + blockNumber, }); // Derive badge stats from full opcode data (SSTORE, SLOAD, LOG*, CREATE*, SELFDESTRUCT) @@ -165,9 +168,6 @@ export function TransactionPage(): JSX.Element { // Get theme colors for charts const colors = useThemeColors(); - // Block number: prefer from response (source of truth), fall back to URL param - const blockNumber = txData?.metadata.blockNumber ?? blockFromSearch ?? null; - // Get contract owners from txData for name lookup (memoized to avoid dependency issues) const contractOwners = useMemo(() => txData?.contractOwners ?? {}, [txData?.contractOwners]); @@ -641,6 +641,29 @@ export function TransactionPage(): JSX.Element { }; }, [contractTree, totalEvmGas, colors]); + // Missing block context (direct URL access without ?block= param) + if (!blockFromSearch) { + return ( + +
+ +
+ + + Back to Gas Profiler + +
+ + ); + } + // Loading state if (isLoading) { return ( diff --git a/src/pages/ethereum/execution/gas-profiler/components/TransactionRow/TransactionRow.tsx b/src/pages/ethereum/execution/gas-profiler/components/TransactionRow/TransactionRow.tsx index eae114b69..38866776b 100644 --- a/src/pages/ethereum/execution/gas-profiler/components/TransactionRow/TransactionRow.tsx +++ b/src/pages/ethereum/execution/gas-profiler/components/TransactionRow/TransactionRow.tsx @@ -67,8 +67,10 @@ export function TransactionRow({ const navigate = useNavigate(); // Fetch full transaction data when expanded + // Always pass blockNumber to match ClickHouse primary key prefix for efficient queries const { data: txData, isLoading: txLoading } = useTransactionGasData({ transactionHash: isExpanded ? transaction.transactionHash : null, + blockNumber, }); // Navigate to call page when a frame is clicked diff --git a/src/pages/ethereum/execution/gas-profiler/hooks/useAllCallFrameOpcodes.ts b/src/pages/ethereum/execution/gas-profiler/hooks/useAllCallFrameOpcodes.ts index ac90e6c91..a519ea97c 100644 --- a/src/pages/ethereum/execution/gas-profiler/hooks/useAllCallFrameOpcodes.ts +++ b/src/pages/ethereum/execution/gas-profiler/hooks/useAllCallFrameOpcodes.ts @@ -23,8 +23,8 @@ export type AllCallFrameOpcodesMap = Map; export interface UseAllCallFrameOpcodesOptions { /** Transaction hash to fetch data for */ transactionHash: string | null; - /** Block number for efficient API queries (uses partition key) */ - blockNumber?: number | null; + /** Block number for efficient API queries (matches primary key prefix) */ + blockNumber: number | null; /** Whether to enable the query (use for toggle) */ enabled?: boolean; } @@ -58,8 +58,7 @@ export function useAllCallFrameOpcodes({ { query: { transaction_hash_eq: transactionHash!, - // Include block_number for efficient partition-based filtering with FINAL - ...(blockNumber ? { block_number_eq: blockNumber } : {}), + block_number_eq: blockNumber!, order_by: 'gas DESC', page_size: 10000, }, @@ -67,7 +66,7 @@ export function useAllCallFrameOpcodes({ 'int_transaction_call_frame_opcode_gas', signal ), - enabled: !!currentNetwork && !!transactionHash && enabled, + enabled: !!currentNetwork && !!transactionHash && !!blockNumber && enabled, staleTime: 5 * 60 * 1000, // Cache for 5 minutes }); diff --git a/src/pages/ethereum/execution/gas-profiler/hooks/useTransactionGasData.ts b/src/pages/ethereum/execution/gas-profiler/hooks/useTransactionGasData.ts index 066d5de09..0cbd39f20 100644 --- a/src/pages/ethereum/execution/gas-profiler/hooks/useTransactionGasData.ts +++ b/src/pages/ethereum/execution/gas-profiler/hooks/useTransactionGasData.ts @@ -52,7 +52,7 @@ export interface TransactionGasData { export interface UseTransactionGasDataOptions { /** Transaction hash to fetch data for */ transactionHash: string | null; - /** Block number for efficient API queries (uses partition key) */ + /** Block number for efficient API queries (matches primary key prefix). Should always be provided when known. */ blockNumber?: number | null; } @@ -299,7 +299,6 @@ export function useTransactionGasData({ { query: { transaction_hash_eq: transactionHash!, - // Include block_number for efficient partition-based filtering with FINAL ...(blockNumber ? { block_number_eq: blockNumber } : {}), order_by: 'call_frame_id ASC', page_size: 10000, @@ -319,7 +318,6 @@ export function useTransactionGasData({ { query: { transaction_hash_eq: transactionHash!, - // Include block_number for efficient partition-based filtering with FINAL ...(blockNumber ? { block_number_eq: blockNumber } : {}), order_by: 'gas DESC', page_size: 10000,