Skip to content

Commit afcaa4c

Browse files
authored
Merge pull request #32 from probe-lab/add-custom-slots
* Allow scan on custom slots, non-missed blocks, or blocks with blobs. * Fixes some connection bans * Target specific clients at the API when giving the eth-panda-ops workbalancer
2 parents b373d1d + 43ddb00 commit afcaa4c

19 files changed

+1323
-256
lines changed

README.md

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ It can extract and present all available information for a given [ENR](https://g
66
- **Relevant ENR Entries**:
77
- `cgc`: Column Custody Group
88
- Computed DataColumn indexes from the Custody Group
9-
- **Node's Chain Status and Metadata (v3)**
9+
- **Node's Chain Status (v1-v2) and Metadata (v2-v3)**
1010
- **Libp2p Information**:
1111
- UserAgent (typically Client + Version)
1212
- Supported protocols
@@ -17,10 +17,10 @@ It can extract and present all available information for a given [ENR](https://g
1717

1818
## Supported Networks
1919

20-
This experimental version of the tool is compatible with existing PeerDAS devnets. It has been tested and confirmed to work with `peerdas-devnet-7`.
20+
This experimental version of the tool is compatible with existing PeerDAS devnets. It has been tested and confirmed to work with `fusaka-devnet-2`.
2121

2222
More details available at:
23-
- EF's [devnet-7 dashboard](https://peerdas-devnet-7.ethpandaops.io/)
23+
- EF's [fusaka-devnet-2 dashboard](https://fusaka-devnet-2.ethpandaops.io/)
2424

2525
## PeerDAS Specifications
2626

@@ -53,66 +53,67 @@ The tool exposes a single CLI command. Basic usage:
5353
$ das-guardian --help
5454

5555
NAME:
56-
das-guardian - An Ethereum DAS custody checker
56+
das-guardian - An ethereum DAS custody checker
5757

5858
USAGE:
59-
das-guardian [global options]
59+
das-guardian [global options] [command [command options]]
60+
61+
COMMANDS:
62+
scan Connects and scans a given node for its custody and network status
63+
monitor Connects and monitors a given node for its custody and network status
64+
help, h Shows a list of commands or help for one command
6065

6166
GLOBAL OPTIONS:
62-
--node.key string ENR of the node to probe
63-
--libp2p.host string IP address for the Libp2p host (default: "127.0.0.1")
64-
--libp2p.port int Port for the Libp2p host (default: 9013)
65-
--api.endpoint string URL of a Beacon API (default: "http://127.0.0.1:5052/")
66-
--connection.retries int Number of connection retries (default: 3)
67-
--connection.timeout duration Connection timeout duration (default: 30s)
68-
--help, -h Show help
67+
--libp2p.host string IP for the Libp2p host (default: "127.0.0.1")
68+
--libp2p.port int Port for the libp2p host (default: 9013)
69+
--api.endpoint string The url endpoint of a Beacon API (http://localhost:5052/) (default: "http://127.0.0.1:5052/")
70+
--api.custom.cl string Name of a custom CL client that we would like to query from the work-balancer ('lighthouse', 'prysm', 'nimbus')
71+
--connection.retries int Number of retries when connecting the node (default: 3)
72+
--connection.timeout duration Timeout for the connection attempt to the node (default: 30s)
73+
--init.timeout duration Timeout to limit the time it can take the guardian to init itself (default: 30s)
74+
--wait.fulu The guardian command will wait until fulu hardfork has happened before proceeding to test the custody (default: true)
75+
--help, -h show help
6976
```
7077
7178
Example of how to use it:
7279
```bash
73-
$ das-guardian \
74-
--api.endpoint "https://beacon.peerdas-devnet-7.ethpandaops.io/" \
75-
--node.key "enr:-Oi4QJpqAuqmnU4iwQLrmyhIt62wrUeexYrTeXsCm06PWLnfPDK99h5mBt4IRmiLzvASKWjw74wsZV9UkzoPVggZj7kah2F0dG5ldHOIAAAAAAAAYACDY2djBIZjbGllbnTXiEdyYW5kaW5ljTEuMS4wLWExNTgwMjeEZXRoMpCDR4TGcFUmR4oCAAAAAAAAgmlkgnY0gmlwhM69sO-EcXVpY4IjKYlzZWNwMjU2azGhAoh6xQUKUjNR3_OtxCO9eOUAfxhTofTAbSFYLfr6a5pWiHN5bmNuZXRzD4N0Y3CCIyiDdWRwgiMo"
76-
77-
INFO[0000] Running eth-das-guardian beacon-api="https://beacon.peerdas-devnet-7.ethpandaops.io/" connection-retries=3 connection-timeout=30s libp2p-host=127.0.0.1 libp2p-port=9013 node-key="enr:-Oi4QJ..."
78-
INFO[0000] Successfully connected to the Beacon API node-version=Lodestar/v1.28.1/1f339ea
79-
INFO[0000] Connected to the Beacon API...
80-
INFO[0002] Downloaded beacon head-state
81-
INFO[0002] * Validators: 336
82-
INFO[0002] * Version: fulu
83-
INFO[0002] * Finalized: false
84-
INFO[0002] * Optimistic EL: false
80+
$ das-guardian --api.endpoint "https://beacon.fusaka-devnet-2.ethpandaops.io/" --api.custom.cl "lighthouse" scan --scan.key "enr:-PO4QFAZca5TDfbiiCKouERBRao_oLgy5KCPvbezPfhTacxHWlBqfDgsfsghRLBUH9W8bj08v1jkd64UoUjSaWZx-6UHh2F0dG5ldHOIAAAAAAADAACDY2djgYCGY2xpZW502IpMaWdodGhvdXNljDcuMS4wLWJldGEuMIRldGgykIEAExpwk3VEAAEAAAAAAACCaWSCdjSCaXCEn99xd4NuZmSENp-J94RxdWljgiMpiXNlY3AyNTZrMaEDzVa77_o452OzzqylcK2mA0DREidLotbGonvz3nogDS-Ic3luY25ldHMPg3RjcIIjKIN1ZHCCIyg"
81+
INFO[0000] running das-guardian beacon-api="https://beacon.fusaka-devnet-2.ethpandaops.io/" beacon-cl-client=lighthouse connection-retries=3 connection-timeout=30s init-timeout=30s libp2p-host=127.0.0.1 libp2p-port=9013 slot-range-number=5 slot-range-slots="[]" slot-range-type=random wait-fulu=true
82+
INFO[0001] successfull connection to the beacon-api node-version=Lighthouse/v7.1.0-beta.0-9993fdf/x86_64-linux
83+
INFO[0001] connected to the beacon API...
84+
INFO[0001] Beacon node identity enr="enr:-O24QMLrZGfQAo8_Svw5lG83kn6XTfiUmMP9Zz6yFayAX1sLNKjAegt04iXwWxVsclGtz0E1Ec77mTe6xT0zlJKdgn2BmIdhdHRuZXRziAAABgAAAAAAg2NnY4GAhmNsaWVudNGKTGlnaHRob3VzZYU3LjEuMIRldGgykLYvKw5wk3VE__________-CaWSCdjSCaXCEpFrLSoNuZmSEti8rDoRxdWljgiMpiXNlY3AyNTZrMaEC-HAEr6PikSNtSPQj7LoDBjzA4lRhjKXzLZMkfPa6c1CIc3luY25ldHMNg3RjcIIjKIN1ZHCCIyg" peer_id=16Uiu2HAmC9UA9nyCov1VAaWPSjycJPLjSLd49SEQWzFpp4EBSa4P
85+
INFO[0005] fulu is supported
86+
INFO[0005] dowloaded beacon head-state
87+
INFO[0005] * version: fulu
88+
INFO[0005] * finalized: false
89+
INFO[0005] * optimistic-el: false
90+
INFO[0005] * validators: 736
91+
INFO[0005] local beacon-status
92+
INFO[0005] * head-slot: 152900
93+
INFO[0005] * fork-digest: 0xb62f2b0e
94+
INFO[0005] local beacon-metadata
95+
INFO[0005] * syncnets: [0]
96+
INFO[0005] * seq-number: 0
97+
INFO[0005] * attnets: [0 0 0 0 0 0 0 0]
98+
INFO[0005] das-guardian initialized peer-id=16Uiu2HAmVMkBPZgCq4oqzEvTBrfkPwwHK3K8HKixCsheKeRFtEAH
99+
INFO[0006] connected to remote node...
100+
INFO[0006] libp2p info...
101+
INFO[0006] * ping_rtt: 105.853822ms
85102
...
86-
INFO[0003] requesting slot-blocks from beacon API... slots="[113791 99432 73995 66414]"
87-
INFO[0005] sampling node for... columns=4 slots=4
88-
INFO[0005] req info... das-result="4/4 columns" req-duration=231.7413ms slot=113791
89-
INFO[0005] req info... das-result="4/4 columns" req-duration=121.785912ms slot=99432
90-
INFO[0005] req info... das-result="4/4 columns" req-duration=118.819413ms slot=73995
91-
INFO[0005] req info... das-result="4/4 columns" req-duration=119.066869ms slot=66414
92-
INFO[0005] node custody sampling done... duration=592.556056ms
93-
┌────────┬────────────────────────────────┬────────────────────────────────┬────────────────────────────────┬────────────────────────────────┐
94-
│ SLOT │ COL [ 28 ] │ COL [ 32 ] │ COL [ 57 ] │ COL [ 117 ] │
95-
├────────┼────────────────────────────────┼────────────────────────────────┼────────────────────────────────┼────────────────────────────────┤
96-
│ 113791 │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │
97-
│ 99432 │ blobs (7/7) / kzg-cmts (7/7/7) │ blobs (7/7) / kzg-cmts (7/7/7) │ blobs (7/7) / kzg-cmts (7/7/7) │ blobs (7/7) / kzg-cmts (7/7/7) │
98-
│ 73995 │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │ blobs (5/5) / kzg-cmts (5/5/5) │
99-
│ 66414 │ blobs (4/4) / kzg-cmts (4/4/4) │ blobs (4/4) / kzg-cmts (4/4/4) │ blobs (4/4) / kzg-cmts (4/4/4) │ blobs (4/4) / kzg-cmts (4/4/4) │
100-
└────────┴────────────────────────────────┴────────────────────────────────┴────────────────────────────────┴────────────────────────────────┘
103+
INFO[0013] sampling node for... columns=128 slots=5
104+
INFO[0014] req info... das-result="128/128 columns" req-duration=1.063251291s slot=99067
105+
INFO[0015] req info... das-result="0/128 columns" req-duration=537.631558ms slot=78000
106+
INFO[0015] req info... das-result="128/128 columns" req-duration=435.80515ms slot=147141
107+
INFO[0016] req info... das-result="0/128 columns" req-duration=189.822971ms slot=107529
108+
INFO[0016] req info... das-result="128/128 columns" req-duration=130.425776ms slot=33121
109+
INFO[0016] node custody sampling done... duration=2.35818963s
101110
```
102111
103112
Note: This tool is a demonstration prototype for debugging CL nodes on devnets. The codebase is experimental — please be gentle regarding its current style and implementation.
104113
105-
## TODOs:
106-
- [ ] structure code on right packages
107-
- [ ] make interface to evaluate the results
108-
- [ ] make logic to limit requested slots for fulu-only slots
109-
- [ ] add nebula logic
110-
- [ ]
111-
112114
## Maintainers
113-
114115
[@cortze](https://github.com/cortze) from [@probe-lab](https://github.com/probe-lab)
116+
[@EthPandaOps](https://github.com/ethpandaops) team
115117
116118
## Contributing
117-
118119
Due to the debugging and research nature of the project, feedback and feature suggestions are very welcome. Feel free to open an issue or submit a pull request.

api/block.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"strings"
78

89
"github.com/attestantio/go-eth2-client/spec"
910
"github.com/attestantio/go-eth2-client/spec/electra"
@@ -19,6 +20,10 @@ type BeaconBlock struct {
1920
Data *electra.SignedBeaconBlock `json:"data"`
2021
}
2122

23+
func (b *BeaconBlock) IsMissed() bool {
24+
return b.Data == nil
25+
}
26+
2227
type FuluBeaconBlock struct {
2328
Slot string `json:"slot"`
2429
ProposerIndex string `json:"proposer_index"`
@@ -34,6 +39,10 @@ func (c *Client) GetBeaconBlock(ctx context.Context, slot uint64) (*spec.Version
3439
beaconBlock := &BeaconBlock{}
3540
resp, err := c.get(ctx, c.cfg.QueryTimeout, fmt.Sprintf(BlockBase, slot), "")
3641
if err != nil {
42+
if strings.Contains(err.Error(), "404 Not Found") {
43+
fmt.Println("not found!")
44+
return new(spec.VersionedSignedBeaconBlock), nil
45+
}
3746
return nil, errors.Wrap(err, "requesting beacon-block")
3847
}
3948
err = json.Unmarshal(resp, &beaconBlock)

api/client.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ import (
1414
)
1515

1616
type ClientConfig struct {
17-
Endpoint string
18-
StateTimeout time.Duration
19-
QueryTimeout time.Duration
20-
Logger log.FieldLogger
17+
Endpoint string
18+
StateTimeout time.Duration
19+
QueryTimeout time.Duration
20+
CustomClClient string
21+
Logger log.FieldLogger
2122
}
2223

2324
type Client struct {
24-
cfg ClientConfig
25-
base *url.URL
26-
client *http.Client
25+
cfg ClientConfig
26+
base *url.URL
27+
client *http.Client
28+
customClClient string
2729
}
2830

2931
func NewClient(cfg ClientConfig) (*Client, error) {
@@ -44,9 +46,10 @@ func NewClient(cfg ClientConfig) (*Client, error) {
4446
}
4547

4648
cli := &Client{
47-
cfg: cfg,
48-
base: urlBase,
49-
client: httpCli,
49+
cfg: cfg,
50+
base: urlBase,
51+
client: httpCli,
52+
customClClient: cfg.CustomClClient,
5053
}
5154

5255
return cli, nil
@@ -85,14 +88,17 @@ func (c *Client) get(
8588

8689
// we will only handle JSONs
8790
req.Header.Set("Accept", "application/json")
91+
// select an specific cl client from the work-balancer if defined
92+
if c.customClClient != "" {
93+
req.Header.Set("X-Dugtrio-Next-Endpoint", c.customClClient)
94+
}
8895

8996
l := c.cfg.Logger.WithFields(log.Fields{
9097
"url": callURL,
9198
"method": req.Method,
9299
})
93100
l.Info("requesting beacon API")
94101
resp, err := c.client.Do(req)
95-
96102
if err != nil {
97103
l.WithError(err).Warn("error requesting beacon API")
98104
return respBody, errors.Wrap(err, fmt.Sprintf("unable to request URL %s", callURL.String()))
@@ -104,6 +110,20 @@ func (c *Client) get(
104110
}
105111
defer resp.Body.Close()
106112

113+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
114+
l.WithFields(log.Fields{
115+
"status_code": resp.StatusCode,
116+
"status": resp.Status,
117+
}).Warn("beacon API returned non-success status code")
118+
119+
// Read the error response body for better error messages
120+
errorBody, readErr := io.ReadAll(resp.Body)
121+
if readErr != nil {
122+
return respBody, fmt.Errorf("unable to read api-response %s", err.Error())
123+
}
124+
return respBody, fmt.Errorf("beacon API request failed %s - %s", resp.Status, string(errorBody))
125+
}
126+
107127
respBody, err = io.ReadAll(resp.Body)
108128
if err != nil {
109129
l.WithError(err).Warn("failed to read response body")

api/client_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ import (
55
"testing"
66
"time"
77

8+
log "github.com/sirupsen/logrus"
89
"github.com/stretchr/testify/require"
910
)
1011

1112
var (
12-
localAvailTestIP = "https://beacon.berlinterop-devnet-2.ethpandaops.io/"
13+
localAvailTestIP = "https://beacon.fusaka-devnet-2.ethpandaops.io/"
1314
StateTimeout = 30 * time.Second
1415
QueryTimeout = 10 * time.Second
1516
)
1617

1718
// API connection
18-
func Test_APIClient(t *testing.T) {
19+
func TestApiClient(t *testing.T) {
1920
httpCli, testMainCtx, cancel := genTestAPICli(t)
2021
defer cancel()
2122

@@ -24,46 +25,54 @@ func Test_APIClient(t *testing.T) {
2425
}
2526

2627
// API endpoints
27-
func Test_ApiGetNodeVersion(t *testing.T) {
28+
func TestApiClient_GetNodeVersion(t *testing.T) {
2829
httpCli, testMainCtx, cancel := genTestAPICli(t)
2930
defer cancel()
3031

3132
_, err := httpCli.GetNodeVersion(testMainCtx)
3233
require.NoError(t, err)
3334
}
3435

35-
func Test_ApiGetPeerDASstate(t *testing.T) {
36+
func TestApiClient_GetPeerDASstate(t *testing.T) {
3637
httpCli, testMainCtx, cancel := genTestAPICli(t)
3738
defer cancel()
3839

3940
_, err := httpCli.GetBeaconStateHead(testMainCtx)
4041
require.NoError(t, err)
4142
}
4243

43-
func Test_ApiGetForkChoice(t *testing.T) {
44+
func TestApiClient_GetForkChoice(t *testing.T) {
4445
httpCli, testMainCtx, cancel := genTestAPICli(t)
4546
defer cancel()
4647

4748
_, err := httpCli.GetForkChoice(testMainCtx)
4849
require.NoError(t, err)
4950
}
5051

51-
func Test_ApiGetNetworkConfig(t *testing.T) {
52+
func TestApiClient_GetNetworkConfig(t *testing.T) {
5253
httpCli, testMainCtx, cancel := genTestAPICli(t)
5354
defer cancel()
5455

5556
_, err := httpCli.GetNetworkConfig(testMainCtx)
5657
require.NoError(t, err)
5758
}
5859

59-
func Test_ApiGetNodeIdentity(t *testing.T) {
60+
func TestApiClient_GetNodeIdentity(t *testing.T) {
6061
httpCli, testMainCtx, cancel := genTestAPICli(t)
6162
defer cancel()
6263

6364
_, err := httpCli.GetNodeIdentity(testMainCtx)
6465
require.NoError(t, err)
6566
}
6667

68+
func TestApiClient_GetConfigSpec(t *testing.T) {
69+
httpCli, testMainCtx, cancel := genTestAPICli(t)
70+
defer cancel()
71+
72+
_, err := httpCli.GetConfigSpecs(testMainCtx)
73+
require.NoError(t, err)
74+
}
75+
6776
// generics
6877
func genTestAPICli(t *testing.T) (*Client, context.Context, context.CancelFunc) {
6978
testMainCtx, cancel := context.WithCancel(context.Background())
@@ -72,6 +81,7 @@ func genTestAPICli(t *testing.T) (*Client, context.Context, context.CancelFunc)
7281
Endpoint: localAvailTestIP,
7382
StateTimeout: StateTimeout,
7483
QueryTimeout: QueryTimeout,
84+
Logger: log.WithFields(log.Fields{}),
7585
}
7686

7787
httpCli, err := NewClient(cfg)

api/fork_choice.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ type ForkChoice struct {
1616
}
1717

1818
type Checkpoint struct {
19-
Epoch uint64 `json:"epoch"`
19+
Epoch string `json:"epoch"`
2020
Root string `json:"root"`
2121
}
2222

2323
type ForkChoiceNode struct {
24-
Slot uint64 `json:"slot"`
24+
Slot string `json:"slot"`
2525
BlockRoot string `json:"block_root"`
2626
ParentRoot string `json:"parent_root"`
2727
JustifiedEpoch string `json:"justified_epoch"`
2828
FinalizedEpoch string `json:"finalized_epoch"`
29-
Weight uint64 `json:"weight"`
29+
Weight string `json:"weight"`
3030
Validity string `json:"validity"`
3131
ExecutionBlockHash string `json:"execution_block_hash"`
3232
}

0 commit comments

Comments
 (0)