Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smooth column in validatorlist #284

Merged
merged 7 commits into from
Jan 29, 2024

Conversation

Marketen
Copy link
Contributor

@Marketen Marketen commented Jan 25, 2024

This PR Adds a new "Smooth" coulmn in KeystoresDataGrid component.

This new column is only rendered in networks where Smooth exists (mainnet or prater). It shows the Smooth subscription status of each validator (only if their tag == "solo").

The rendering logic is done in a new file SmoothStatus.tsx https://github.com/dappnode/StakingBrain/blob/marc/smooth-status-integration/packages/ui/src/components/ValidatorList/SmoothStatus.tsx#L50

The oracle call is a method inside KeystoresDataGrid.tsx: https://github.com/dappnode/StakingBrain/blob/marc/smooth-status-integration/packages/ui/src/components/ValidatorList/KeystoresDataGrid.tsx#L91

Screenshot_20240125_155607

image

@Marketen Marketen marked this pull request as ready for review January 25, 2024 15:12
@Marketen Marketen requested a review from a team as a code owner January 25, 2024 15:12

export const MAINNET_ORACLE_URL = "https://sp-api.dappnode.io" as const;

export const TESTNET_ORACLE_URL = "http://65.109.102.216:7300" as const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to have a domain for the testnet oracle, it is a bit weird to have the IP and port hardcoded here, but since we don't have it it's ok...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can ask for a goerli domain, but for now we should either keep it like this or remove prater oracle integration

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep it, but let's update it in the future if possible

const fetchValidatorsData = async (rows: CustomValidatorGetResponse[]) => {
const newValidatorSubscriptionStatus: SmoothStatusByPubkey = {};
// Filter rows to include only those with an index
const rowsWithIndex = rows.filter(row => row.index);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you set a specific icon for those validators that have not received a deposit yet? It could be great to have this information given to the user

Copy link
Contributor

@dappnodedev dappnodedev Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltip like: "No index found for this validator. Are your clients synced? Have you done a deposit? "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 0 index are found --> consensus client probably offline. Oracle call returns error, so a red "?" is rendered with error message when hovering.
https://github.com/dappnode/StakingBrain/blob/marc/smooth-status-integration/packages/ui/src/components/ValidatorList/SmoothStatus.tsx#L65

If only some index could not be found --> their subscriptionStatus is not fetched (does not exist) so they are stuck in "Loading..." I could improve this but its not that bad neither https://github.com/dappnode/StakingBrain/blob/marc/smooth-status-integration/packages/ui/src/components/ValidatorList/SmoothStatus.tsx#L74


// Healthy checks: check that the response is valid and that the response is ok
if (!isValidOracleResponse(data)) {
throw new Error(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could define a specific error for this? Something like OracleApiError? (just a suggestion)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think its worth it for now


// find the validator in the batch and mark it as not subscribed
const validator = batch.find(
(validator) => validator.index === notFoundValiToString
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could avoid creating a variable here, you do not use anywhere else (notFoundValidator.toString()). It's also ok to create it if the code looks cleaner/simpler

Copy link
Contributor Author

@Marketen Marketen Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reafactored this part


// Only proceed if there is at least one row with an index
// new validatos will not have an index yet
if (rowsWithIndex.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could remove this and include inside the try-catch:

if (rowsWithIndex.length < 1 ) throw ... (error message)

I suggest this because you already handle setting the oracle error on the catch. This way you could avoid this duplication:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderCell: (rowData: { row: any; }) => {
// only render smooth status if tag is "solo"
if (rowData.row.tag === "solo") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be checked, but probably you can set Smooth for any tag that has an editable fee recipient, not only solo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now, lets keep oracle status only to "solo" tag validators

/>
);
} else {
return <span>-</span>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could try to find an emoji like 🚫 or something similar that lets the user know it is not available for that protocol

Copy link
Contributor Author

@Marketen Marketen Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this emoji as "banned" status. I want the column to not have noise when validator is not "solo", so went for the "-"


const feeRecipient = rowData.row.feeRecipient;
const withdrawalFormat = rowData.row.withdrawalCredentials.format;
const mevSpAddress = mevSpFeeRecipient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

; missing here

}
// subscriptionStatus is null when oracleCall hasnt finished yet
if (!subscriptionStatus) {
return <span>Loading...</span>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would return a loading spinner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done! looks way better


// unhealthy subscription. Wrong fee recipient
} else if (
(subscriptionStatus.toLowerCase() === "active" ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might need to add extra parenthesis here:
(cond1 OR cond2 OR cond3) AND cond4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there already is right?

else if (
    (subscriptionStatus.toLowerCase() === "active" ||
      subscriptionStatus.toLowerCase() === "yellowcard" ||
      subscriptionStatus.toLowerCase() === "redcard") &&
    rowData.row.feeRecipient !== mevSpAddress
  )

an extra parentesis encapsulates all 3 "healthy" possible oracle states. If a validator is in any of these and has bad fee recipient, he is in danger of being banned

@@ -61,6 +69,106 @@ export default function KeystoresDataGrid({
openDashboardTab();
}, [validatorSummaryURL]);

useState<CustomValidatorUpdateRequest>();
const [validatorsSubscriptionMap, setValidatorsData] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following best practices, the useState should have the same naming in the const and the setter: const [validatorsData, setValidatorsData]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -61,6 +69,106 @@ export default function KeystoresDataGrid({
openDashboardTab();
}, [validatorSummaryURL]);

useState<CustomValidatorUpdateRequest>();
const [validatorsSubscriptionMap, setValidatorsData] =
useState<SmoothStatusByPubkey>({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider as "dangerous" setting a initial value an empty object, it might lead to future issues since it will be considered as a non-nullish value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. changed it to be initialized as null, not in danger of being considered non-nullish anymore.


// Check that Smooth API returns an expected response format
function isValidOracleResponse(response: SmoothValidatorByIndexApiResponse): boolean {
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be out of context, why are we expecting those values as null? could you add a comment pls?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect them to either exist or to be null.

The oracle call recieves the validator's indeces loaded in brain and returns two arrays, found_validators and not_found_validators. A validator that never interacted with oracle will be inside not_found_validator. A validator that at some point has already interacted with the oracle will be inside found_validators

For example, if a user loads web3signer brain for the first time and has never subscribed to smooth, we expect found_validators to be null, since its validators have not interactuated with the oracle yet and it is not tracking them.

const validator = batch.find(
(validator) => validator.index === notFoundValiToString
);
if (validator) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be taken any action if validator not found?

Copy link
Contributor Author

@Marketen Marketen Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have refactored this part, all "not found" validators will have the unsubscribed status now. It wasnt clear before


// Call the Oracle API to get the subscription status of the validators in the batch by their index
const response = await fetch(
`${apiUrl}/memory/validatorsbyindex/${batch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you querying all the validators in the smooth? not see the index arg or I might need to have a look to the smooth api to fully understand

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im querying only the validators loaded in brain with index available (notice that new validators have to wait 16-24h to be indexed by the chain)

        const oracleApiResponse = await fetch(
          `${apiUrl}/memory/validatorsbyindex/${batch
            .map((row) => row.index)
            .join(",")}`
        );

}
} else {
setOracleCallError(
"Skipping Oracle subscription status fetch, no validator index could be fetch. Is your consensus client synced?"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be implemented in a separated PR, but, can we know in advanced if is synced or not? this way we would display useful data instead of an error with a "maybe"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! Didnt, think about it, oracle has an endpoint that tells you if it is synced. I have implemented it in code now, just before trying to call subscription status (if not sync, subscription status call is skipped)

@@ -74,7 +182,7 @@ export default function KeystoresDataGrid({
field: "feeRecipient",
headerName: "Fee Recipient",
description:
"Fee Recipient is a feature that lets you specify a priority fee recipient address on your validator client instance and beacon node",
"Address to which the rewards generated from proposing a block are sent",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better description!

@pablomendezroyo pablomendezroyo merged commit bc4a347 into main Jan 29, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants