Merge branch 'feature/finalized-slot-state' into develop

This commit is contained in:
Willian Mitsuda 2022-08-29 17:22:35 -03:00
commit 8a5fca753e
No known key found for this signature in database
6 changed files with 115 additions and 16 deletions

View File

@ -6,43 +6,41 @@ It depends heavily on a working Erigon installation with Otterscan patches appli
## Install Erigon ## Install Erigon
You will need an Erigon executing node (`erigon`). Also you will need Erigon RPC daemon (`rpcdaemon`) with Otterscan patches. Since setting up an Erigon environment itself can take some work, make sure to follow their instructions and have a working archive node before continuing. You will need an Erigon executing node (`erigon`) with Otterscan patches. Since setting up an Erigon environment itself can take some work, make sure to follow their instructions and have a working archive node before continuing.
My personal experience: at the moment of this writing (~block 14,000,000), setting up an archive node takes over 5-6 days and ~1.7 TB of SSD. My personal experience: at the moment of this writing (~block 15,000,000), setting up an archive node takes over 3-7 days (depending on your hardware) and ~1.6 TB of SSD.
They have weekly stable releases, make sure you are running on of them, not development ones. They have weekly alpha releases, make sure you are running one of them, not development ones.
## Install Otterscan-patched rpcdaemon ## Install Otterscan-patched erigon
We rely on custom JSON-RPC APIs which are not available in a standard ETH node. We keep a separated repository containing an Erigon fork here: https://github.com/wmitsuda/erigon. We rely on custom JSON-RPC APIs which are not available in a standard ETH node. We keep a separated repository containing an Erigon fork here: https://github.com/wmitsuda/erigon.
Please follow the instructions in the repository `README` and replace the original Erigon `rpcdaemon` with our patched one. Please follow the instructions in the repository `README` and replace the original Erigon `erigon` with our patched one.
## Enable Otterscan namespace on rpcdaemon ## Enable Otterscan namespace on erigon
When running `rpcdaemon`, make sure to enable the `erigon`, `ots`, `eth` APIs in addition to whatever cli options you are using to start `rpcdaemon`. When running `erigon`, make sure to enable the `erigon`, `ots`, `eth` APIs in addition to whatever cli options you are using to start `erigon`.
`ots` stands for Otterscan and it is the namespace we use for our own custom APIs. `ots` stands for Otterscan and it is the namespace we use for our own custom APIs.
``` ```
<path-to-rpcdaemon-binary>/rpcdaemon --http.api "eth,erigon,ots,<your-other-apis>" --private.api.addr 127.0.0.1:9090 --datadir <erigon-datadir> --http.corsdomain "*" <path-to-erigon-binary>/erigon --http.api "eth,erigon,ots,<your-other-apis>" --datadir <erigon-datadir> --http.corsdomain "*"
``` ```
Be sure to include both `--private.api.addr` and `--datadir` parameter so you run it in dual mode, otherwise the performance will be much worse. Pay attention to the `--http.corsdomain` parameter, CORS is **required** for the browser to call the node directly.
Also pay attention to the `--http.corsdomain` parameter, CORS is **required** for the browser to call the node directly. Now you should have an Erigon node with Otterscan JSON-RPC APIs and CORS enabled.
Now you should have an Erigon node with Otterscan JSON-RPC APIs enabled, running in dual mode with CORS enabled.
## Run Otterscan docker image from Docker Hub ## Run Otterscan docker image from Docker Hub
The Otterscan official repo on Docker Hub is [here](https://hub.docker.com/orgs/otterscan/repositories). The Otterscan official repo on Docker Hub is [here](https://hub.docker.com/orgs/otterscan/repositories).
``` ```
docker run --rm -p 5000:80 --name otterscan -d otterscan/otterscan:<versiontag> docker run --rm -p 5100:80 --name otterscan -d otterscan/otterscan:<versiontag>
``` ```
This will download the Otterscan image from Docker Hub, run it locally using the default parameters, binding it to port 5000 (see the `-p` docker run parameter). This will download the Otterscan image from Docker Hub, run it locally using the default parameters, binding it to port 5100 (see the `-p` docker run parameter).
To stop Otterscan service, run: To stop Otterscan service, run:
@ -53,11 +51,27 @@ docker stop otterscan
By default it assumes your Erigon node is at `http://127.0.0.1:8545`. You can override the URL by setting the `ERIGON_URL` env variable on `docker run`: By default it assumes your Erigon node is at `http://127.0.0.1:8545`. You can override the URL by setting the `ERIGON_URL` env variable on `docker run`:
``` ```
docker run --rm -p 5000:80 --name otterscan -d --env ERIGON_URL="<your-erigon-node-url>" otterscan/otterscan:<versiontag> docker run --rm -p 5100:80 --name otterscan -d --env ERIGON_URL="<your-erigon-node-url>" otterscan/otterscan:<versiontag>
``` ```
This is the preferred way to run Otterscan. You can read about other ways [here](./other-ways-to-run-otterscan.md). This is the preferred way to run Otterscan. You can read about other ways [here](./other-ways-to-run-otterscan.md).
## (Optional) Enable integration with beacon chain
You can optionally enable displaying extra info from the beacon chain by providing the public URL of your beacon node API.
Enabling the beacon chain API depends on which CL implementation you are using.
> As an example, for Prysm you need to enable CORS and possibly bind the address to the correct network interface with `--grpc-gateway-host="0.0.0.0" --grpc-gateway-corsdomain='*'` and by default it binds it to the port 3500.
When starting the Otterscan process via Docker, you need to add an extra env variable called `BEACON_API_URL` pointing to your beacon node API URL.
Prysm example:
```
docker run --rm -p 5100:80 --name otterscan -d --env BEACON_API_URL="<your-beacon-node-api-url>" otterscan/otterscan:<versiontag>
```
## Validating the installation (all methods) ## Validating the installation (all methods)
You can make sure it is working correctly if the homepage is able to show the latest block/timestamp your Erigon node is at just bellow the search button. You can make sure it is working correctly if the homepage is able to show the latest block/timestamp your Erigon node is at just bellow the search button.

View File

@ -1,4 +1,5 @@
{ {
"erigonURL": "http://localhost:8545", "erigonURL": "http://localhost:8545",
"beaconAPI": null,
"assetsURLPrefix": "http://localhost:3001" "assetsURLPrefix": "http://localhost:3001"
} }

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
PARAMS="{\"erigonURL\": $(echo $ERIGON_URL | jq -aR .), \"assetsURLPrefix\": \"\"}" PARAMS="{\"erigonURL\": $(echo $ERIGON_URL | jq -aR .), \"beaconAPI\": $(echo $BEACON_API_URL | jq -aR .), \"assetsURLPrefix\": \"\"}"
echo $PARAMS > /usr/share/nginx/html/config.json echo $PARAMS > /usr/share/nginx/html/config.json
nginx -g "daemon off;" nginx -g "daemon off;"

View File

@ -10,6 +10,7 @@ import { RuntimeContext } from "./useRuntime";
import { useLatestBlockHeader } from "./useLatestBlock"; import { useLatestBlockHeader } from "./useLatestBlock";
import { blockURL } from "./url"; import { blockURL } from "./url";
import { useGenericSearch } from "./search/search"; import { useGenericSearch } from "./search/search";
import { useFinalizedSlot, useSlotTime } from "./useBeacon";
const CameraScanner = React.lazy(() => import("./search/CameraScanner")); const CameraScanner = React.lazy(() => import("./search/CameraScanner"));
@ -18,6 +19,8 @@ const Home: React.FC = () => {
const [searchRef, handleChange, handleSubmit] = useGenericSearch(); const [searchRef, handleChange, handleSubmit] = useGenericSearch();
const latestBlock = useLatestBlockHeader(provider); const latestBlock = useLatestBlockHeader(provider);
const beaconData = useFinalizedSlot();
const slotTime = useSlotTime(beaconData?.data.header.message.slot);
const [isScanning, setScanning] = useState<boolean>(false); const [isScanning, setScanning] = useState<boolean>(false);
document.title = "Home | Otterscan"; document.title = "Home | Otterscan";
@ -87,6 +90,20 @@ const Home: React.FC = () => {
<Timestamp value={latestBlock.timestamp} /> <Timestamp value={latestBlock.timestamp} />
</NavLink> </NavLink>
)} )}
{beaconData && (
<div className="flex flex-col items-center space-y-1 mt-5 text-sm text-gray-500">
<div>
Finalized slot: {commify(beaconData.data.header.message.slot)}
</div>
{slotTime && <Timestamp value={slotTime} />}
<div>
State root:{" "}
<span className="font-hash">
{beaconData.data.header.message.state_root}
</span>
</div>
</div>
)}
</div> </div>
</div> </div>
); );

66
src/useBeacon.ts Normal file
View File

@ -0,0 +1,66 @@
import { useContext } from "react";
import useSWR from "swr";
import useSWRImmutable from "swr/immutable";
import { RuntimeContext } from "./useRuntime";
// 12s
const SLOT_TIME = 12;
// TODO: remove duplication with other json fetchers
const jsonFetcher = async (url: string) => {
try {
const res = await fetch(url);
if (res.ok) {
return res.json();
}
return null;
} catch (err) {
console.warn(`error while getting beacon data: url=${url} err=${err}`);
return null;
}
};
export const useFinalizedSlot = () => {
const { config } = useContext(RuntimeContext);
// Each slot is 12s, so program SWR to revalidate at this interval
const { data, error } = useSWR(
config?.beaconAPI
? `${config?.beaconAPI}/eth/v1/beacon/headers/finalized`
: null,
jsonFetcher,
{
revalidateOnFocus: false,
refreshInterval: SLOT_TIME * 1000,
}
);
if (error) {
return undefined;
}
return data;
};
export const useBeaconGenesis = () => {
const { config } = useContext(RuntimeContext);
const { data, error } = useSWRImmutable(
config?.beaconAPI ? `${config?.beaconAPI}/eth/v1/beacon/genesis` : null,
jsonFetcher
);
if (error) {
return undefined;
}
return data;
};
export const useSlotTime = (slot: number | undefined): number | undefined => {
const genesis = useBeaconGenesis();
if (slot === undefined || genesis === undefined) {
return undefined;
}
const rawDate = genesis.data.genesis_time;
return parseInt(rawDate) + slot * SLOT_TIME;
};

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
export type OtterscanConfig = { export type OtterscanConfig = {
erigonURL?: string; erigonURL?: string;
beaconAPI?: string;
assetsURLPrefix?: string; assetsURLPrefix?: string;
}; };