diff --git a/client/nodeset.go b/client/nodeset.go index 315eb2d..07d51c5 100644 --- a/client/nodeset.go +++ b/client/nodeset.go @@ -1,8 +1,6 @@ package swclient import ( - "strconv" - "github.com/ethereum/go-ethereum/common" swapi "github.com/nodeset-org/hyperdrive-stakewise/shared/api" "github.com/rocket-pool/node-manager-core/api/client" @@ -38,9 +36,7 @@ func (r *NodesetRequester) SetValidatorsRoot(root common.Hash) (*types.ApiRespon } // Upload the aggregated deposit data file to NodeSet's servers -func (r *NodesetRequester) UploadDepositData(bypassBalanceCheck bool) (*types.ApiResponse[swapi.NodesetUploadDepositDataData], error) { - args := map[string]string{ - "bypassBalanceCheck": strconv.FormatBool(bypassBalanceCheck), - } +func (r *NodesetRequester) UploadDepositData() (*types.ApiResponse[swapi.NodesetUploadDepositDataData], error) { + args := map[string]string{} return client.SendGetRequest[swapi.NodesetUploadDepositDataData](r, "upload-deposit-data", "UploadDepositData", args) } diff --git a/common/nodeset-client.go b/common/nodeset-client.go index 14e6291..7c0cf2a 100644 --- a/common/nodeset-client.go +++ b/common/nodeset-client.go @@ -258,3 +258,22 @@ func (c *NodesetClient) submitRequest(ctx context.Context, method string, body i logger.Debug("NodeSet response:", slog.String(log.CodeKey, resp.Status), slog.String(log.BodyKey, string(bytes))) return bytes, nil } + +func IsUploadedToNodeset(pubKey beacon.ValidatorPubkey, registeredPubkeys []beacon.ValidatorPubkey) bool { + for _, registeredPubKey := range registeredPubkeys { + if registeredPubKey == pubKey { + return true + } + } + return false +} + +func IsRegisteredToStakewise(pubKey beacon.ValidatorPubkey, statuses map[beacon.ValidatorPubkey]beacon.ValidatorStatus) bool { + // TODO: Implement + return false +} + +func IsUploadedStakewise(pubKey beacon.ValidatorPubkey, statuses map[beacon.ValidatorPubkey]beacon.ValidatorStatus) bool { + // TODO: Implement + return false +} diff --git a/go.mod b/go.mod index 9369a26..2d38d21 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515155018-d97df5b61692 + github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515161352-9f17d9f9bfa5 github.com/rocket-pool/batch-query v1.0.0 github.com/rocket-pool/node-manager-core v0.3.1-0.20240515153751-dd88806cef89 github.com/urfave/cli/v2 v2.27.1 diff --git a/go.sum b/go.sum index 6f1c223..5a837d1 100644 --- a/go.sum +++ b/go.sum @@ -236,8 +236,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515155018-d97df5b61692 h1:LAHl/tj+l4UIZjJv+73S1EIb0stK3AGyw5X9odfC9x4= -github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515155018-d97df5b61692/go.mod h1:cPMY0KDqaoJXN2dUkC1ChhfjsguHDPLYcSbBskIPb/s= +github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515161352-9f17d9f9bfa5 h1:VyLPdNB285Rp6brVrJk7u5VfbdVKMBx6ilXciXCX8VI= +github.com/nodeset-org/hyperdrive-daemon v0.4.2-dev.0.20240515161352-9f17d9f9bfa5/go.mod h1:cPMY0KDqaoJXN2dUkC1ChhfjsguHDPLYcSbBskIPb/s= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= diff --git a/server/nodeset/upload-deposit-data.go b/server/nodeset/upload-deposit-data.go index 584deff..e4cb7b3 100644 --- a/server/nodeset/upload-deposit-data.go +++ b/server/nodeset/upload-deposit-data.go @@ -7,18 +7,23 @@ import ( "net/url" "github.com/goccy/go-json" + eth2types "github.com/wealdtech/go-eth2-types/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/gorilla/mux" + swcommon "github.com/nodeset-org/hyperdrive-stakewise/common" + "github.com/rocket-pool/node-manager-core/eth" + duserver "github.com/nodeset-org/hyperdrive-daemon/module-utils/server" swapi "github.com/nodeset-org/hyperdrive-stakewise/shared/api" - "github.com/rocket-pool/node-manager-core/api/server" "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/beacon" - "github.com/rocket-pool/node-manager-core/eth" - "github.com/rocket-pool/node-manager-core/utils/input" "github.com/rocket-pool/node-manager-core/wallet" - eth2types "github.com/wealdtech/go-eth2-types/v2" +) + +const ( + pendingState string = "PENDING" + validatorDepositCost float64 = 0.01 ) // =============== @@ -33,9 +38,7 @@ func (f *nodesetUploadDepositDataContextFactory) Create(args url.Values) (*nodes c := &nodesetUploadDepositDataContext{ handler: f.handler, } - inputErrs := []error{ - server.ValidateArg("bypassBalanceCheck", args, input.ValidateBool, &c.bypassBalanceCheck), - } + inputErrs := []error{} return c, errors.Join(inputErrs...) } @@ -48,10 +51,8 @@ func (f *nodesetUploadDepositDataContextFactory) RegisterRoute(router *mux.Route // =============== // === Context === // =============== - type nodesetUploadDepositDataContext struct { - handler *NodesetHandler - bypassBalanceCheck bool + handler *NodesetHandler } func (c *nodesetUploadDepositDataContext) PrepareData(data *swapi.NodesetUploadDepositDataData, walletStatus wallet.WalletStatus, opts *bind.TransactOpts) (types.ResponseStatus, error) { @@ -61,86 +62,112 @@ func (c *nodesetUploadDepositDataContext) PrepareData(data *swapi.NodesetUploadD w := sp.GetWallet() ec := sp.GetEthClient() ctx := c.handler.ctx - + data.EthPerKey = validatorDepositCost // Requirements err := sp.RequireStakewiseWalletReady(ctx, walletStatus) if err != nil { return types.ResponseStatus_WalletNotReady, err } - - // Get the list of registered validators - registeredPubkeyMap := map[beacon.ValidatorPubkey]bool{} - pubkeyStatusResponse, err := nc.GetRegisteredValidators(ctx) + // Fetch status from Nodeset APIs + nodesetStatusResponse, err := nc.GetRegisteredValidators(ctx) if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting registered validators: %w", err) + return types.ResponseStatus_Error, fmt.Errorf("error getting registered validators from Nodeset: %w", err) } - registeredPubkeys := []beacon.ValidatorPubkey{} - for _, pubkeyStatus := range pubkeyStatusResponse { - registeredPubkeys = append(registeredPubkeys, pubkeyStatus.Pubkey) + + // Fetch private keys and derive public keys + privateKeys, err := w.GetAllPrivateKeys() + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting private keys: %w", err) } - for _, pubkey := range registeredPubkeys { - registeredPubkeyMap[pubkey] = true + + privateKeyMap := make(map[beacon.ValidatorPubkey]*eth2types.BLSPrivateKey) + publicKeyMap := make(map[beacon.ValidatorPubkey]bool) + publicKeys := []beacon.ValidatorPubkey{} + for _, privateKey := range privateKeys { + pubkey := beacon.ValidatorPubkey(privateKey.PublicKey().Marshal()) + publicKeys = append(publicKeys, pubkey) + publicKeyMap[pubkey] = true + privateKeyMap[pubkey] = privateKey } - // Get the list of this node's validator keys - keys, err := w.GetAllPrivateKeys() - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting private validator keys: %w", err) + activePubkeysOnNodeset := []beacon.ValidatorPubkey{} + pendingPubkeysOnNodeset := []beacon.ValidatorPubkey{} + + for _, validator := range nodesetStatusResponse { + _, exists := publicKeyMap[validator.Pubkey] + if exists { + if validator.Status != pendingState { + activePubkeysOnNodeset = append(activePubkeysOnNodeset, validator.Pubkey) + } else { + pendingPubkeysOnNodeset = append(pendingPubkeysOnNodeset, validator.Pubkey) + } + } } - data.TotalCount = uint64(len(keys)) - // Find the ones that haven't been uploaded yet + // Process public keys based on their status unregisteredKeys := []*eth2types.BLSPrivateKey{} - newPubkeys := []beacon.ValidatorPubkey{} - for _, key := range keys { - pubkey := beacon.ValidatorPubkey(key.PublicKey().Marshal()) - _, exists := registeredPubkeyMap[pubkey] - if !exists { - unregisteredKeys = append(unregisteredKeys, key) - newPubkeys = append(newPubkeys, pubkey) + data.TotalCount = uint64(len(publicKeys)) - uint64(len(pendingPubkeysOnNodeset)) + // Used for displaying the unregistered keys in the response + unregisteredPubkeys := []beacon.ValidatorPubkey{} + + for _, pubkey := range publicKeys { + if !swcommon.IsUploadedToNodeset(pubkey, activePubkeysOnNodeset) && !swcommon.IsUploadedToNodeset(pubkey, pendingPubkeysOnNodeset) { + unregisteredKeys = append(unregisteredKeys, privateKeyMap[pubkey]) + unregisteredPubkeys = append(unregisteredPubkeys, pubkey) } } - data.UnregisteredPubkeys = newPubkeys - if len(unregisteredKeys) == 0 { - return types.ResponseStatus_Success, nil + // Determine if sufficient balance is available for deposits + balance, err := ec.BalanceAt(ctx, opts.From, nil) + if err != nil { + return types.ResponseStatus_Error, fmt.Errorf("error getting balance: %w", err) } + data.Balance = eth.WeiToEth(balance) - // Make sure validator has enough funds to pay for the deposit - if !c.bypassBalanceCheck { - balance, err := ec.BalanceAt(ctx, opts.From, nil) - if err != nil { - return types.ResponseStatus_Error, fmt.Errorf("error getting balance: %w", err) - } - data.Balance = balance + totalCost := big.NewInt(0) + costPerKey := eth.EthToWei(validatorDepositCost) + + unregisteredKeysCount := len(unregisteredKeys) + pendingPubkeysOnNodesetCount := len(pendingPubkeysOnNodeset) - totalCost := big.NewInt(int64(len(unregisteredKeys))) - totalCost.Mul(totalCost, eth.EthToWei(0.01)) - data.RequiredBalance = totalCost + totalCostForKeys := big.NewInt(0).Mul(costPerKey, big.NewInt(int64(unregisteredKeysCount+pendingPubkeysOnNodesetCount))) + totalCost.Add(totalCost, totalCostForKeys) - data.SufficientBalance = (totalCost.Cmp(balance) < 0) - if !data.SufficientBalance { - return types.ResponseStatus_Success, nil + data.SufficientBalance = (totalCost.Cmp(balance) <= 0) + + if !data.SufficientBalance { + for totalCost.Cmp(balance) > 0 { + unregisteredKeys = unregisteredKeys[1:] + unregisteredPubkeys = unregisteredPubkeys[1:] + totalCost.Sub(totalCost, costPerKey) } + data.UnregisteredPubkeys = unregisteredPubkeys + } + + remainingKeys := uint64(len(publicKeys)) - uint64(len(data.UnregisteredPubkeys)) - uint64(len(pendingPubkeysOnNodeset)) + remainingCost := big.NewInt(0) + if remainingKeys > 0 { + remainingCost = big.NewInt(0).Mul(costPerKey, big.NewInt(int64(remainingKeys))) } + data.RemainingEthRequired = eth.WeiToEth(remainingCost) - // Get the deposit data for those pubkeys + if len(unregisteredKeys) == 0 { + return types.ResponseStatus_Success, nil + } + // Generate deposit data and submit depositData, err := ddMgr.GenerateDepositData(unregisteredKeys) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error generating deposit data: %w", err) } - - // Serialize it - bytes, err := json.Marshal(depositData) + serializedData, err := json.Marshal(depositData) if err != nil { return types.ResponseStatus_Error, fmt.Errorf("error serializing deposit data: %w", err) } - - // Submit the upload - response, err := nc.UploadDepositData(ctx, bytes) - if err != nil { + if response, err := nc.UploadDepositData(ctx, serializedData); err != nil { return types.ResponseStatus_Error, err + } else { + data.ServerResponse = response } - data.ServerResponse = response + return types.ResponseStatus_Success, nil } diff --git a/server/status/get-validator-statuses.go b/server/status/get-validator-statuses.go index 21093e9..6d9aa43 100644 --- a/server/status/get-validator-statuses.go +++ b/server/status/get-validator-statuses.go @@ -5,8 +5,10 @@ import ( "fmt" "net/url" + swcommon "github.com/nodeset-org/hyperdrive-stakewise/common" swapi "github.com/nodeset-org/hyperdrive-stakewise/shared/api" swtypes "github.com/nodeset-org/hyperdrive-stakewise/shared/types" + "github.com/rocket-pool/node-manager-core/api/types" "github.com/rocket-pool/node-manager-core/beacon" "github.com/rocket-pool/node-manager-core/wallet" @@ -100,11 +102,11 @@ func (c *statusGetValidatorsStatusesContext) PrepareData(data *swapi.ValidatorSt // NodeSet status switch { - case isRegisteredToStakewise(pubkey, beaconStatusResponse): + case swcommon.IsRegisteredToStakewise(pubkey, beaconStatusResponse): state.NodesetStatus = swtypes.NodesetStatus_RegisteredToStakewise - case isUploadedStakewise(pubkey, beaconStatusResponse): + case swcommon.IsUploadedStakewise(pubkey, beaconStatusResponse): state.NodesetStatus = swtypes.NodesetStatus_UploadedStakewise - case isUploadedToNodeset(pubkey, registeredPubkeys): + case swcommon.IsUploadedToNodeset(pubkey, registeredPubkeys): state.NodesetStatus = swtypes.NodesetStatus_UploadedToNodeset default: state.NodesetStatus = swtypes.NodesetStatus_Generated @@ -113,22 +115,3 @@ func (c *statusGetValidatorsStatusesContext) PrepareData(data *swapi.ValidatorSt return types.ResponseStatus_Success, nil } - -func isRegisteredToStakewise(pubKey beacon.ValidatorPubkey, statuses map[beacon.ValidatorPubkey]beacon.ValidatorStatus) bool { - // TODO: Implement - return false -} - -func isUploadedStakewise(pubKey beacon.ValidatorPubkey, statuses map[beacon.ValidatorPubkey]beacon.ValidatorStatus) bool { - // TODO: Implement - return false -} - -func isUploadedToNodeset(pubKey beacon.ValidatorPubkey, registeredPubkeys []beacon.ValidatorPubkey) bool { - for _, registeredPubKey := range registeredPubkeys { - if registeredPubKey == pubKey { - return true - } - } - return false -} diff --git a/shared/api/nodeset.go b/shared/api/nodeset.go index de43855..5e927bc 100644 --- a/shared/api/nodeset.go +++ b/shared/api/nodeset.go @@ -1,16 +1,15 @@ package swapi import ( - "math/big" - "github.com/rocket-pool/node-manager-core/beacon" ) type NodesetUploadDepositDataData struct { - SufficientBalance bool `json:"sufficientBalance"` - Balance *big.Int `json:"balance"` - RequiredBalance *big.Int `json:"requiredBalance"` - ServerResponse []byte `json:"serverResponse"` - UnregisteredPubkeys []beacon.ValidatorPubkey `json:"newPubkeys"` - TotalCount uint64 `json:"totalCount"` + SufficientBalance bool `json:"sufficientBalance"` + Balance float64 `json:"balance"` + ServerResponse []byte `json:"serverResponse"` + UnregisteredPubkeys []beacon.ValidatorPubkey `json:"newPubkeys"` + TotalCount uint64 `json:"totalCount"` + EthPerKey float64 `json:"ethPerKey"` + RemainingEthRequired float64 `json:"remainingEthRequired"` }