Skip to content

Commit 6012422

Browse files
committed
Show latest block withdrawls processed
1 parent cdec3b8 commit 6012422

5 files changed

Lines changed: 113 additions & 2 deletions

File tree

rocketpool-cli/megapool/exit-validator.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package megapool
33
import (
44
"fmt"
55
"sort"
6+
"strings"
67

78
"github.com/rocket-pool/smartnode/shared/services/beacon"
89
"github.com/rocket-pool/smartnode/shared/services/rocketpool"
@@ -26,7 +27,26 @@ func getExitableValidator() (uint64, bool, error) {
2627
defer rp.Close()
2728

2829
// Get the latest block and identify the withdrawals present in it
29-
30+
withdrawalsResp, err := rp.GetLatestBlockWithdrawals()
31+
if err != nil {
32+
fmt.Printf("Warning: could not fetch latest beacon block withdrawals: %s\n\n", err.Error())
33+
} else if len(withdrawalsResp.Withdrawals) == 0 {
34+
fmt.Printf("Latest beacon block (slot %d, exec block %d) has no validator withdrawals.\n\n",
35+
withdrawalsResp.Slot, withdrawalsResp.BlockNumber)
36+
} else {
37+
indexes := make([]string, 0, len(withdrawalsResp.Withdrawals))
38+
seen := make(map[string]struct{}, len(withdrawalsResp.Withdrawals))
39+
for _, wd := range withdrawalsResp.Withdrawals {
40+
if _, ok := seen[wd.ValidatorIndex]; ok {
41+
continue
42+
}
43+
seen[wd.ValidatorIndex] = struct{}{}
44+
indexes = append(indexes, wd.ValidatorIndex)
45+
}
46+
fmt.Printf("Latest beacon block (slot %d, exec block %d) processed withdrawals for %d validator(s):\n",
47+
withdrawalsResp.Slot, withdrawalsResp.BlockNumber, len(indexes))
48+
fmt.Printf(" %s\n\n", strings.Join(indexes, ", "))
49+
}
3050

3151
// Get Megapool status
3252
status, err := rp.MegapoolStatus(false)
@@ -50,7 +70,9 @@ func getExitableValidator() (uint64, bool, error) {
5070
}
5171
}
5272
if len(exitingValidators) > 0 {
53-
sort.Sort(ByIndex(exitingValidators))
73+
// Make sure that exitingValidators is sorted by validator index ascending from the last withdrawal index
74+
75+
//sort.Sort(ByIndex(exitingValidators))
5476
fmt.Println("The following validators are still active and have already received their exit request on the Beacon Chain:")
5577
for _, v := range exitingValidators {
5678
fmt.Printf("ID %d: - Index %d Pubkey: 0x%s\n", v.ValidatorId, v.ValidatorIndex, v.PubKey.String())
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package megapool
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/urfave/cli/v3"
7+
8+
"github.com/rocket-pool/smartnode/shared/services"
9+
"github.com/rocket-pool/smartnode/shared/services/beacon"
10+
"github.com/rocket-pool/smartnode/shared/types/api"
11+
)
12+
13+
// getLatestBlockWithdrawals returns the validator withdrawals processed in the
14+
// latest beacon block that contains an execution payload. If the head slot has
15+
// no execution payload (e.g. it was a missed slot), it walks backwards a few
16+
// slots until it finds one.
17+
func getLatestBlockWithdrawals(c *cli.Command) (*api.LatestBlockWithdrawalsResponse, error) {
18+
if err := services.RequireBeaconClientSynced(c); err != nil {
19+
return nil, err
20+
}
21+
bc, err := services.GetBeaconClient(c)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
blockToRequest := "head"
27+
const maxAttempts = 8
28+
var (
29+
block beacon.BeaconBlock
30+
exists bool
31+
)
32+
for attempts := 0; attempts < maxAttempts; attempts++ {
33+
block, exists, err = bc.GetBeaconBlock(blockToRequest)
34+
if err != nil {
35+
return nil, fmt.Errorf("error getting beacon block %s: %w", blockToRequest, err)
36+
}
37+
if exists && block.HasExecutionPayload {
38+
break
39+
}
40+
// Walk backwards by slot number; if we don't yet have one, fall back.
41+
var nextSlot uint64
42+
if block.Slot > 0 {
43+
nextSlot = block.Slot - 1
44+
} else if attempts == 0 {
45+
// We never resolved the head, give up
46+
return nil, fmt.Errorf("could not resolve the head beacon block")
47+
}
48+
if attempts == maxAttempts-1 {
49+
return nil, fmt.Errorf("could not find a beacon block with an execution payload after %d attempts", maxAttempts)
50+
}
51+
blockToRequest = fmt.Sprintf("%d", nextSlot)
52+
}
53+
54+
response := &api.LatestBlockWithdrawalsResponse{
55+
Slot: block.Slot,
56+
BlockNumber: block.ExecutionBlockNumber,
57+
Withdrawals: block.Withdrawals,
58+
}
59+
return response, nil
60+
}

rocketpool/api/megapool/routes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ func RegisterRoutes(mux *http.ServeMux, c *cli.Command) {
362362
resp, err := getEffectiveDelegate(c)
363363
apiutils.WriteResponse(w, resp, err)
364364
})
365+
366+
mux.HandleFunc("/api/megapool/latest-block-withdrawals", func(w http.ResponseWriter, r *http.Request) {
367+
resp, err := getLatestBlockWithdrawals(c)
368+
apiutils.WriteResponse(w, resp, err)
369+
})
365370
}
366371

367372
func parseUint64(r *http.Request, name string) (uint64, error) {

shared/services/rocketpool/megapool.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,22 @@ func (c *Client) DistributeMegapool() (api.DistributeMegapoolResponse, error) {
557557
return response, nil
558558
}
559559

560+
// Get the validator withdrawals processed in the latest beacon block (with execution payload)
561+
func (c *Client) GetLatestBlockWithdrawals() (api.LatestBlockWithdrawalsResponse, error) {
562+
responseBytes, err := c.callHTTPAPI("GET", "/api/megapool/latest-block-withdrawals", nil)
563+
if err != nil {
564+
return api.LatestBlockWithdrawalsResponse{}, fmt.Errorf("Could not get latest block withdrawals: %w", err)
565+
}
566+
var response api.LatestBlockWithdrawalsResponse
567+
if err := json.Unmarshal(responseBytes, &response); err != nil {
568+
return api.LatestBlockWithdrawalsResponse{}, fmt.Errorf("Could not decode latest block withdrawals response: %w", err)
569+
}
570+
if response.Error != "" {
571+
return api.LatestBlockWithdrawalsResponse{}, fmt.Errorf("Could not get latest block withdrawals: %s", response.Error)
572+
}
573+
return response, nil
574+
}
575+
560576
// Get the bond amount required for the megapool's next validator
561577
func (c *Client) GetNewValidatorBondRequirement() (api.GetNewValidatorBondRequirementResponse, error) {
562578
responseBytes, err := c.callHTTPAPI("GET", "/api/megapool/get-new-validator-bond-requirement", nil)

shared/types/api/megapool.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,11 @@ type GetNodeMegapoolEthBondedResponse struct {
178178
Error string `json:"error"`
179179
EthBonded *big.Int `json:"ethBonded"`
180180
}
181+
182+
type LatestBlockWithdrawalsResponse struct {
183+
Status string `json:"status"`
184+
Error string `json:"error"`
185+
Slot uint64 `json:"slot"`
186+
BlockNumber uint64 `json:"blockNumber"`
187+
Withdrawals []beacon.WithdrawalInfo `json:"withdrawals"`
188+
}

0 commit comments

Comments
 (0)