Merge branch 'feature/finalized-slot-state' into develop
This commit is contained in:
commit
8a5fca753e
|
@ -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.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"erigonURL": "http://localhost:8545",
|
"erigonURL": "http://localhost:8545",
|
||||||
|
"beaconAPI": null,
|
||||||
"assetsURLPrefix": "http://localhost:3001"
|
"assetsURLPrefix": "http://localhost:3001"
|
||||||
}
|
}
|
|
@ -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;"
|
||||||
|
|
17
src/Home.tsx
17
src/Home.tsx
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue