Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidation, withdrawal & exit request creation tool #166

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading