Skip to content

Commit

Permalink
Merge pull request #166 from ethpandaops/pk910/trigger-el-requests
Browse files Browse the repository at this point in the history
Consolidation, withdrawal & exit request creation tool
  • Loading branch information
pk910 authored Nov 22, 2024
2 parents ff6fef0 + 7909de3 commit 58e4b0b
Show file tree
Hide file tree
Showing 29 changed files with 2,168 additions and 17 deletions.
1 change: 1 addition & 0 deletions .hack/devnet/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ frontend:
validatorNamesYaml: "${__dir}/generated-validator-ranges.yaml"
showSensitivePeerInfos: true
showSubmitDeposit: true
showSubmitElRequests: true
beaconapi:
localCacheSize: 10
redisCacheAddr: ""
Expand Down
1 change: 1 addition & 0 deletions clients/consensus/chainspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type ChainSpec struct {
MaxConsolidationRequestsPerPayload uint64 `yaml:"MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD"`
MaxWithdrawalRequestsPerPayload uint64 `yaml:"MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD"`
DepositChainId uint64 `yaml:"DEPOSIT_CHAIN_ID"`
MinActivationBalance uint64 `yaml:"MIN_ACTIVATION_BALANCE"`

// EIP7594: PeerDAS
NumberOfColumns *uint64 `yaml:"NUMBER_OF_COLUMNS"`
Expand Down
2 changes: 2 additions & 0 deletions cmd/dora-explorer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ func startFrontend(webserver *http.Server) {
router.HandleFunc("/validators/slashings", handlers.Slashings).Methods("GET")
router.HandleFunc("/validators/el_withdrawals", handlers.ElWithdrawals).Methods("GET")
router.HandleFunc("/validators/el_consolidations", handlers.ElConsolidations).Methods("GET")
router.HandleFunc("/validators/submit_consolidations", handlers.SubmitConsolidation).Methods("GET")
router.HandleFunc("/validators/submit_withdrawals", handlers.SubmitWithdrawal).Methods("GET")
router.HandleFunc("/validator/{idxOrPubKey}", handlers.Validator).Methods("GET")
router.HandleFunc("/validator/{index}/slots", handlers.ValidatorSlots).Methods("GET")

Expand Down
1 change: 1 addition & 0 deletions config/default.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ frontend:
showSensitivePeerInfos: false
showPeerDASInfos: false
showSubmitDeposit: false
showSubmitElRequests: false

beaconapi:
# beacon node rpc endpoints
Expand Down
30 changes: 23 additions & 7 deletions handlers/pageData.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,31 @@ func createMenuItems(active string) []types.MainMenuItem {
},
})

submitLinks := []types.NavigationLink{}
if utils.Config.Frontend.ShowSubmitDeposit {
submitLinks = append(submitLinks, types.NavigationLink{
Label: "Submit Deposits",
Path: "/validators/deposits/submit",
Icon: "fa-file-import",
})
}

if utils.Config.Frontend.ShowSubmitElRequests {
submitLinks = append(submitLinks, types.NavigationLink{
Label: "Submit Consolidations",
Path: "/validators/submit_consolidations",
Icon: "fa-square-plus",
})
submitLinks = append(submitLinks, types.NavigationLink{
Label: "Submit Withdrawals & Exits",
Path: "/validators/submit_withdrawals",
Icon: "fa-money-bill-transfer",
})
}

if len(submitLinks) > 0 {
validatorMenu = append(validatorMenu, types.NavigationGroup{
Links: []types.NavigationLink{
{
Label: "Submit Deposits",
Path: "/validators/deposits/submit",
Icon: "fa-file-import",
},
},
Links: submitLinks,
})
}

Expand Down
160 changes: 160 additions & 0 deletions handlers/submit_consolidation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package handlers

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"

v1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/ethereum/go-ethereum/common"
"github.com/sirupsen/logrus"

"github.com/ethpandaops/dora/indexer/execution"
"github.com/ethpandaops/dora/services"
"github.com/ethpandaops/dora/templates"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
)

// SubmitConsolidation will submit a consolidation request
func SubmitConsolidation(w http.ResponseWriter, r *http.Request) {
var submitConsolidationTemplateFiles = append(layoutTemplateFiles,
"submit_consolidation/submit_consolidation.html",
)
var pageTemplate = templates.GetTemplate(submitConsolidationTemplateFiles...)

if !utils.Config.Frontend.ShowSubmitElRequests {
handlePageError(w, r, errors.New("submit el requests is not enabled"))
return
}

query := r.URL.Query()
if query.Has("ajax") {
err := handleSubmitConsolidationPageDataAjax(w, r)
if err != nil {
handlePageError(w, r, err)
}
return
}

pageData, pageError := getSubmitConsolidationPageData()
if pageError != nil {
handlePageError(w, r, pageError)
return
}
if pageData == nil {
data := InitPageData(w, r, "blockchain", "/submit_consolidation", "Submit Consolidation", submitConsolidationTemplateFiles)
w.Header().Set("Content-Type", "text/html")
if handleTemplateError(w, r, "submit_consolidation.go", "Submit Consolidation", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil {
return // an error has occurred and was processed
}
return
}

data := InitPageData(w, r, "blockchain", "/submit_consolidation", "Submit Consolidation", submitConsolidationTemplateFiles)
data.Data = pageData
w.Header().Set("Content-Type", "text/html")
if handleTemplateError(w, r, "submit_consolidation.go", "Submit Consolidation", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil {
return // an error has occurred and was processed
}
}

func getSubmitConsolidationPageData() (*models.SubmitConsolidationPageData, error) {
pageData := &models.SubmitConsolidationPageData{}
pageCacheKey := "submit_consolidation"
pageRes, pageErr := services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} {
pageData, cacheTimeout := buildSubmitConsolidationPageData()
pageCall.CacheTimeout = cacheTimeout
return pageData
})
if pageErr == nil && pageRes != nil {
resData, resOk := pageRes.(*models.SubmitConsolidationPageData)
if !resOk {
return nil, ErrInvalidPageModel
}
pageData = resData
}
return pageData, pageErr
}

func buildSubmitConsolidationPageData() (*models.SubmitConsolidationPageData, time.Duration) {
logrus.Debugf("submit consolidation page called")

chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()

pageData := &models.SubmitConsolidationPageData{
NetworkName: specs.ConfigName,
PublicRPCUrl: utils.Config.Frontend.PublicRPCUrl,
RainbowkitProjectId: utils.Config.Frontend.RainbowkitProjectId,
ChainId: specs.DepositChainId,
ConsolidationContract: execution.ConsolidationContractAddr,
ExplorerUrl: utils.Config.Frontend.EthExplorerLink,
}

return pageData, 1 * time.Hour
}

func handleSubmitConsolidationPageDataAjax(w http.ResponseWriter, r *http.Request) error {
query := r.URL.Query()
var pageData interface{}

switch query.Get("ajax") {
case "load_validators":
address := query.Get("address")
addressBytes := common.HexToAddress(address)

validators := services.GlobalBeaconService.GetCachedValidatorSet()
result := []models.SubmitConsolidationPageDataValidator{}
for _, validator := range validators {
if validator.Validator.WithdrawalCredentials[0] == 0x00 {
continue
}

if !bytes.Equal(validator.Validator.WithdrawalCredentials[12:], addressBytes[:]) {
continue
}

var status string
if strings.HasPrefix(validator.Status.String(), "pending") {
status = "Pending"
} else if validator.Status == v1.ValidatorStateActiveOngoing {
status = "Active"
} else if validator.Status == v1.ValidatorStateActiveExiting {
status = "Exiting"
} else if validator.Status == v1.ValidatorStateActiveSlashed {
status = "Slashed"
} else if validator.Status == v1.ValidatorStateExitedUnslashed {
status = "Exited"
} else if validator.Status == v1.ValidatorStateExitedSlashed {
status = "Slashed"
} else {
status = validator.Status.String()
}

result = append(result, models.SubmitConsolidationPageDataValidator{
Index: uint64(validator.Index),
Pubkey: validator.Validator.PublicKey.String(),
Balance: uint64(validator.Balance),
CredType: fmt.Sprintf("%02x", validator.Validator.WithdrawalCredentials[0]),
Status: status,
})
}

pageData = result
default:
return errors.New("invalid ajax request")
}

w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(pageData)
if err != nil {
logrus.WithError(err).Error("error encoding index data")
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
}
return nil
}
161 changes: 161 additions & 0 deletions handlers/submit_withdrawal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package handlers

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"

v1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/ethereum/go-ethereum/common"
"github.com/sirupsen/logrus"

"github.com/ethpandaops/dora/indexer/execution"
"github.com/ethpandaops/dora/services"
"github.com/ethpandaops/dora/templates"
"github.com/ethpandaops/dora/types/models"
"github.com/ethpandaops/dora/utils"
)

// SubmitWithdrawal will submit a withdrawal request
func SubmitWithdrawal(w http.ResponseWriter, r *http.Request) {
var submitWithdrawalTemplateFiles = append(layoutTemplateFiles,
"submit_withdrawal/submit_withdrawal.html",
)
var pageTemplate = templates.GetTemplate(submitWithdrawalTemplateFiles...)

if !utils.Config.Frontend.ShowSubmitElRequests {
handlePageError(w, r, errors.New("submit el requests is not enabled"))
return
}

query := r.URL.Query()
if query.Has("ajax") {
err := handleSubmitWithdrawalPageDataAjax(w, r)
if err != nil {
handlePageError(w, r, err)
}
return
}

pageData, pageError := getSubmitWithdrawalPageData()
if pageError != nil {
handlePageError(w, r, pageError)
return
}
if pageData == nil {
data := InitPageData(w, r, "blockchain", "/submit_withdrawal", "Submit Withdrawals & Exits", submitWithdrawalTemplateFiles)
w.Header().Set("Content-Type", "text/html")
if handleTemplateError(w, r, "submit_withdrawal.go", "Submit Withdrawals & Exits", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil {
return // an error has occurred and was processed
}
return
}

data := InitPageData(w, r, "blockchain", "/submit_withdrawal", "Submit Withdrawals & Exits", submitWithdrawalTemplateFiles)
data.Data = pageData
w.Header().Set("Content-Type", "text/html")
if handleTemplateError(w, r, "submit_withdrawal.go", "Submit Withdrawals & Exits", "", pageTemplate.ExecuteTemplate(w, "layout", data)) != nil {
return // an error has occurred and was processed
}
}

func getSubmitWithdrawalPageData() (*models.SubmitWithdrawalPageData, error) {
pageData := &models.SubmitWithdrawalPageData{}
pageCacheKey := "submit_withdrawal"
pageRes, pageErr := services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} {
pageData, cacheTimeout := buildSubmitWithdrawalPageData()
pageCall.CacheTimeout = cacheTimeout
return pageData
})
if pageErr == nil && pageRes != nil {
resData, resOk := pageRes.(*models.SubmitWithdrawalPageData)
if !resOk {
return nil, ErrInvalidPageModel
}
pageData = resData
}
return pageData, pageErr
}

func buildSubmitWithdrawalPageData() (*models.SubmitWithdrawalPageData, time.Duration) {
logrus.Debugf("submit withdrawal page called")

chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()

pageData := &models.SubmitWithdrawalPageData{
NetworkName: specs.ConfigName,
PublicRPCUrl: utils.Config.Frontend.PublicRPCUrl,
RainbowkitProjectId: utils.Config.Frontend.RainbowkitProjectId,
ChainId: specs.DepositChainId,
WithdrawalContract: execution.WithdrawalContractAddr,
ExplorerUrl: utils.Config.Frontend.EthExplorerLink,
MinValidatorBalance: specs.MinActivationBalance,
}

return pageData, 1 * time.Hour
}

func handleSubmitWithdrawalPageDataAjax(w http.ResponseWriter, r *http.Request) error {
query := r.URL.Query()
var pageData interface{}

switch query.Get("ajax") {
case "load_validators":
address := query.Get("address")
addressBytes := common.HexToAddress(address)

validators := services.GlobalBeaconService.GetCachedValidatorSet()
result := []models.SubmitWithdrawalPageDataValidator{}
for _, validator := range validators {
if validator.Validator.WithdrawalCredentials[0] == 0x00 {
continue
}

if !bytes.Equal(validator.Validator.WithdrawalCredentials[12:], addressBytes[:]) {
continue
}

var status string
if strings.HasPrefix(validator.Status.String(), "pending") {
status = "Pending"
} else if validator.Status == v1.ValidatorStateActiveOngoing {
status = "Active"
} else if validator.Status == v1.ValidatorStateActiveExiting {
status = "Exiting"
} else if validator.Status == v1.ValidatorStateActiveSlashed {
status = "Slashed"
} else if validator.Status == v1.ValidatorStateExitedUnslashed {
status = "Exited"
} else if validator.Status == v1.ValidatorStateExitedSlashed {
status = "Slashed"
} else {
status = validator.Status.String()
}

result = append(result, models.SubmitWithdrawalPageDataValidator{
Index: uint64(validator.Index),
Pubkey: validator.Validator.PublicKey.String(),
Balance: uint64(validator.Balance),
CredType: fmt.Sprintf("%02x", validator.Validator.WithdrawalCredentials[0]),
Status: status,
})
}

pageData = result
default:
return errors.New("invalid ajax request")
}

w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(pageData)
if err != nil {
logrus.WithError(err).Error("error encoding index data")
http.Error(w, "Internal server error", http.StatusServiceUnavailable)
}
return nil
}
Loading

0 comments on commit 58e4b0b

Please sign in to comment.