Skip to content

Commit

Permalink
Merge pull request #207 from shutter-network/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
ylembachar authored Oct 30, 2024
2 parents 11fa1b8 + 3038ce3 commit 911c8f3
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 91 deletions.
89 changes: 89 additions & 0 deletions frontend/cypress/components/SlotProgression.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { MemoryRouter } from 'react-router-dom';
import SlotProgression from '../../src/modules/SlotProgression';
import { mount } from "cypress/react18";
import React from 'react';
import { ThemeProvider as MUIThemeProvider } from '@mui/material/styles';
import { ThemeProvider as StyledThemeProvider } from 'styled-components';
import { muiTheme, customTheme } from '../../src/theme';

describe('<SlotProgression />', () => {
const gnosisGenesisTime = 1638993340;
const slotDuration = 5;
const slotsPerEpoch = 16;

it('verifies slot states using explicit class names with controlled time', () => {
const currentTime = Date.now();
cy.clock(currentTime);
cy.stub(Date, 'now').returns(currentTime);
const currentSlot = Math.floor((currentTime / 1000 - gnosisGenesisTime) / slotDuration);

const mockSlots = Array.from({ length: slotsPerEpoch * 3 }, (_, i) => ({
Slot: currentSlot - slotsPerEpoch + i,
ValidatorIndex: 1000 + i,
IsRegisteration: i % 2 == 0,
}));

cy.intercept('GET', '/api/slot/top_5_epochs', {
statusCode: 200,
body: {
message: mockSlots,
},
}).as('getSlotData');

mount(
<MUIThemeProvider theme={muiTheme}>
<StyledThemeProvider theme={customTheme}>
<MemoryRouter>
<SlotProgression />
</MemoryRouter>
</StyledThemeProvider>
</MUIThemeProvider>
);

cy.wait('@getSlotData').then(({ response }) => {
if (!response?.body?.message) {
throw new Error('API response is undefined or improperly structured');
}

const slotsData = response.body.message;

cy.get('.slot-visualizer').should('exist').then(() => {
cy.get('.slot-visualizer').find('.slot-block').each(($slot, index) => {
const slotData = slotsData[index];
cy.wrap($slot).as(`slot-${index}`);

if (slotData.Slot == currentSlot) {
cy.get(`@slot-${index}`).should('have.class', 'active');
} else if (slotData.Slot < currentSlot) {
cy.get(`@slot-${index}`).should('have.class', 'passed');
}

if (slotData.IsRegisteration) {
cy.get(`@slot-${index}`).should('have.class', 'shutterized');
cy.get(`@slot-${index}`).find('svg').should('exist');
}
});
});

cy.tick(slotDuration * 1000);
const newCurrentSlot = currentSlot + 1;

cy.get('.slot-visualizer').find('.slot-block').each(($slot, index) => {
const slotData = slotsData[index];
cy.wrap($slot).as(`slot-${index}`);

if (slotData.Slot === newCurrentSlot) {
cy.get(`@slot-${index}`).should('have.class', 'active');
} else if (slotData.Slot < newCurrentSlot) {
cy.get(`@slot-${index}`).should('have.class', 'passed');
}

if (slotData.IsRegisteration) {
cy.get(`@slot-${index}`).should('have.class', 'shutterized');
cy.get(`@slot-${index}`).find('svg').should('exist');
}
});

});
});
});
2 changes: 1 addition & 1 deletion frontend/cypress/components/Transaction.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('<Transaction />', () => {
);

cy.wait('@getTransaction');
cy.contains('Inclusion Timeout Expired').should('be.visible');
cy.contains('INCLUSION TIMEOUT EXPIRED').should('be.visible');
cy.contains('(Please check for inclusion directly on the Gnosis Explorer)').should('be.visible');
cy.get(`a[href*="${explorerUrl}/tx/${notIncludedTransactionData.UserTxHash}"]`).should('be.visible');
});
Expand Down
6 changes: 3 additions & 3 deletions frontend/cypress/utils/transactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const explorerUrl = Cypress.env('REACT_APP_EXPLORER_URL') as string;
export const verifySubmittedTransactionDetails = (data: TransactionData): void => {
cy.contains('Transaction Details').should('be.visible');
cy.contains('Transaction Status').should('be.visible');
cy.contains(data.TxStatus).should('be.visible');
cy.contains(data.TxStatus.toUpperCase()).should('be.visible');
cy.contains('Estimated Inclusion Time').should('be.visible');
cy.contains(formatTimestamp(false, data.InclusionTime!)).should('be.visible');
cy.contains('Estimated Inclusion Delay').should('be.visible');
Expand All @@ -69,7 +69,7 @@ export const verifySubmittedTransactionDetails = (data: TransactionData): void =
export const verifyPendingTransactionDetails = (data: TransactionData): void => {
cy.contains('Transaction Details').should('be.visible');
cy.contains('Transaction Status').should('be.visible');
cy.contains(data.TxStatus).should('be.visible');
cy.contains(data.TxStatus.toUpperCase()).should('be.visible');
cy.contains('Estimated Inclusion Time').should('be.visible');
cy.contains(formatTimestamp(false, data.InclusionTime!)).should('be.visible');
cy.contains('Estimated Inclusion Delay').should('be.visible');
Expand All @@ -84,7 +84,7 @@ export const verifyPendingTransactionDetails = (data: TransactionData): void =>
export const verifyTransactionDetailsUpdated = (data: TransactionData): void => {
cy.contains('Transaction Details').should('be.visible');
cy.contains('Transaction Status').should('be.visible');
cy.contains(data.TxStatus).should('be.visible');
cy.contains(data.TxStatus.toUpperCase()).should('be.visible');
cy.contains('Effective Inclusion Time').should('be.visible');
cy.contains(formatTimestamp(true, data.InclusionTime!)).should('be.visible');
cy.contains('Effective Inclusion Delay').should('be.visible');
Expand Down
93 changes: 58 additions & 35 deletions frontend/src/modules/SlotProgression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
StyledShutterLogoIcon,
} from '../styles/slotProgression';

const calculateCurrentSlotAndEpoch = (genesisTime: number, slotDuration: number) => {
const currentTime = Math.floor(Date.now() / 1000);
const currentSlot = Math.floor((currentTime - genesisTime) / slotDuration);
const calculateCurrentSlotAndEpoch = (genesisTime: number, slotDuration: number, currentTime = Date.now()) => {
const currentSlot = Math.floor((currentTime / 1000 - genesisTime) / slotDuration);
console.log("Current slot in code" + currentSlot);
const currentEpoch = Math.floor(currentSlot / 16);
const relativeSlotIndex = currentSlot % 16;
return { currentEpoch, relativeSlotIndex, currentSlot };
Expand All @@ -24,8 +24,7 @@ const calculateCurrentSlotAndEpoch = (genesisTime: number, slotDuration: number)
const SlotProgression = () => {
const [forceRefetch, setForceRefetch] = useState(false);
const { data: slotData, loading: loadingSlots, error: errorSlots } = useFetch('/api/slot/top_5_epochs', forceRefetch);
const [currentEpochSlots, setCurrentEpochSlots] = useState<any[]>([]);
const [nextEpochSlots, setNextEpochSlots] = useState<any[]>([]);
const [epochsSlots, setEpochsSlots] = useState<any[]>([]);
const [currentEpoch, setCurrentEpoch] = useState<number>(0);
const [currentSlotIndex, setCurrentSlotIndex] = useState<number>(0);

Expand All @@ -38,22 +37,28 @@ const SlotProgression = () => {
console.log('Initial slot data received from API:', slotData);

const sortedSlots = slotData.message.sort((a: any, b: any) => a.Slot - b.Slot);
const { currentEpoch, relativeSlotIndex } = calculateCurrentSlotAndEpoch(gnosisGenesisTime, slotDuration);

const currentEpochSlotsData = sortedSlots.filter(
(slot: any) => slot.Slot >= currentEpoch * 16 && slot.Slot < (currentEpoch + 1) * 16
);
const nextEpochSlotsData = sortedSlots.filter(
(slot: any) => slot.Slot >= (currentEpoch + 1) * 16 && slot.Slot < (currentEpoch + 2) * 16
);
const epochsMap = new Map<number, any[]>();
sortedSlots.forEach((slot: any) => {
const epoch = Math.floor(slot.Slot / 16);
if (!epochsMap.has(epoch)) {
epochsMap.set(epoch, []);
}
epochsMap.get(epoch)?.push(slot);
});

setCurrentEpochSlots(currentEpochSlotsData); // Load current epoch slots
setNextEpochSlots(nextEpochSlotsData); // Preload next epoch slots
const epochsData = Array.from(epochsMap.entries()).map(([epoch, slots]) => ({
epoch,
slots,
}));

setEpochsSlots(epochsData);
const { currentEpoch, relativeSlotIndex } = calculateCurrentSlotAndEpoch(gnosisGenesisTime, slotDuration);
setCurrentEpoch(currentEpoch);
setCurrentSlotIndex(relativeSlotIndex);

console.log('Current Epoch Slots:', currentEpochSlotsData);
console.log('Next Epoch Slots:', nextEpochSlotsData);
console.log('Epochs Data:', epochsData);
console.log('Current Epoch:', currentEpoch, 'Current Slot Index:', relativeSlotIndex);
} else {
console.warn('Invalid slot data structure received from the API:', slotData);
}
Expand All @@ -67,23 +72,19 @@ const SlotProgression = () => {

if (prevIndex < currentEpochEndSlot) {
console.log('Moving to the next slot within the current epoch');
return prevIndex + 1; // Move to the next slot within the current epoch
return prevIndex + 1;
} else {
console.log('Transitioning to the next epoch');
setCurrentEpoch((prevEpoch) => prevEpoch + 1);
setCurrentEpochSlots(nextEpochSlots); // Use the preloaded next epoch slots as the current epoch slots
setNextEpochSlots([]); // Clear the next epoch slots temporarily
setForceRefetch((prev) => !prev); // Toggle the forceRefetch state to trigger a data refetch
setForceRefetch((prev) => !prev); // Trigger a data refetch for new epoch data

return 0; // Reset slot index to the beginning of the new epoch
}
});
}, slotDuration * 1000);

return () => clearInterval(slotInterval);
}, [nextEpochSlots]);

const slotsToDisplay = currentEpochSlots;
}, []);

const renderSlotBlock = (slot: any) => {
const isCurrentSlot = slot.Slot === currentSlotIndex + currentEpoch * 16;
Expand All @@ -102,27 +103,49 @@ const SlotProgression = () => {
);
};

const filteredEpochsSlots = epochsSlots.filter(({ epoch }) =>
epoch >= currentEpoch - 1 && epoch <= currentEpoch + 1
);

const currentEpochSlots = epochsSlots.find((epoch) => epoch.epoch === currentEpoch);
const currentSlotData = currentEpochSlots?.slots[currentSlotIndex];

return (
<Box sx={{ flexGrow: 1, marginTop: 4 }}>
<Box sx={{ flexGrow: 1 }}>
{errorSlots ? (
<Alert severity="error">Error fetching Slot data: {errorSlots.message}</Alert>
) : (
<SlotDetailsWrapper>
<Typography variant="h2" align="left">Epoch {currentEpoch}</Typography>
<SlotVisualizer>
{loadingSlots ? (
<p>Loading slot progression...</p>
) : (
slotsToDisplay.map((slot: any) => renderSlotBlock(slot))
)}
</SlotVisualizer>
{loadingSlots ? (
<p>Loading slot progression...</p>
) : (
filteredEpochsSlots.map(({ epoch, slots }) => (
<div key={epoch}>
<Typography variant="h2" align="left" paddingTop={"20px"}>
Epoch {epoch}
</Typography>
<SlotVisualizer>
{slots.map((slot: any) => renderSlotBlock(slot))}
</SlotVisualizer>
</div>
))
)}

<PreviousSlotDetails>
<Typography variant="h2" align="left">Slot Details</Typography>
<DetailsGrid>
<DetailCard title="Slot number" value={slotsToDisplay[currentSlotIndex]?.Slot || 'N/A'} />
<DetailCard title="Validator Index" value={slotsToDisplay[currentSlotIndex]?.ValidatorIndex || 'N/A'} />
<DetailCard title="Shutterized" value={slotsToDisplay[currentSlotIndex]?.IsRegisteration ? 'Yes' : 'No'} />
<DetailCard
title="Slot number"
value={currentSlotData?.Slot || 'N/A'}
/>
<DetailCard
title="Validator Index"
value={currentSlotData?.ValidatorIndex || 'N/A'}
/>
<DetailCard
title="Shutterized"
value={currentSlotData?.IsRegisteration ? 'Yes' : 'No'}
/>
</DetailsGrid>
</PreviousSlotDetails>
</SlotDetailsWrapper>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Slot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const Slot = () => {
<ResponsiveLayout>
<Box sx={{ flexGrow: 1, marginTop: 4 }}>
<TitleSection title="Slot Overview" />
<Typography variant="body1" textAlign="left" paddingTop="20px" color="#051016" lineHeight="180%">
<Typography variant="body1" textAlign="left" paddingTop="20px" lineHeight="180%" fontSize={"18px"}>
The slot overview is designed to give you information about the activity around the current blockchain's state.
It gives a look ahead to the next 16 slots and shows when a registered validator is scheduled.
A green background means that the slot has passed. The Shutter logo means the corresponding proposer will include encrypted transactions.
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ const Transaction: FC = () => {
<Grid container spacing={1}>
<Grid>
<Typography variant="body1" className={`tx-status status-${transaction.TxStatus.replace(/\s+/g, "-")}`}>
{transaction.TxStatus === 'Not included' ? 'Inclusion Timeout Expired' : transaction.TxStatus}
{transaction.TxStatus === 'Not included' ? 'INCLUSION TIMEOUT EXPIRED' : transaction.TxStatus.toUpperCase()}
</Typography>
</Grid>
{transaction.TxStatus === 'Not included' && (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/TransactionLookup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const TransactionLookup: FC = () => {
<>
<TitleSection title="Transaction Lookup" />

<Typography variant="body1" textAlign="left" color="#051016" lineHeight="180%">
<Typography variant="body1" textAlign="left" paddingTop="20px" lineHeight="180%" fontSize={"18px"}>
<h2> How to Use the Transaction Lookup on Shutter Explorer </h2>
When you send a transaction using Shutter, it remains encrypted and won’t show up on Gnosisscan until it’s processed.
To help you track your transaction status while it’s still shielded, we created the Transaction Lookup tool on Shutter Explorer.
Expand Down
Loading

0 comments on commit 911c8f3

Please sign in to comment.