diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6f5e46cd3..0c17a7d0d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -20,6 +20,9 @@ jobs: with: go-version: 1.19 + - name: Test with the Go CLI + run: go test ./... + - name: Golangci uses: golangci/golangci-lint-action@v3 with: diff --git a/shared/services/bc-manager.go b/shared/services/bc-manager.go index 3f3c7dfa7..5ddc44ffa 100644 --- a/shared/services/bc-manager.go +++ b/shared/services/bc-manager.go @@ -134,6 +134,16 @@ func (m *BeaconClientManager) GetSyncStatus() (beacon.SyncStatus, error) { return result.(beacon.SyncStatus), nil } +func (m *BeaconClientManager) GetNodeVersion() (beacon.NodeVersion, error) { + result, err := m.runFunction1(func(client beacon.Client) (interface{}, error) { + return client.GetNodeVersion() + }) + if err != nil { + return beacon.NodeVersion{}, err + } + return result.(beacon.NodeVersion), nil +} + // Get the Beacon configuration func (m *BeaconClientManager) GetEth2Config() (beacon.Eth2Config, error) { result, err := m.runFunction1(func(client beacon.Client) (interface{}, error) { diff --git a/shared/services/beacon/client.go b/shared/services/beacon/client.go index dcbf32b5f..f11276d2b 100644 --- a/shared/services/beacon/client.go +++ b/shared/services/beacon/client.go @@ -97,6 +97,10 @@ type AttestationInfo struct { CommitteeIndex uint64 } +type NodeVersion struct { + Version string +} + // Beacon client type type BeaconClientType int @@ -133,6 +137,7 @@ const ( type Client interface { GetClientType() (BeaconClientType, error) GetSyncStatus() (SyncStatus, error) + GetNodeVersion() (NodeVersion, error) GetEth2Config() (Eth2Config, error) GetEth2DepositContract() (Eth2DepositContract, error) GetAttestations(blockId string) ([]AttestationInfo, bool, error) diff --git a/shared/services/beacon/client/std-http-client.go b/shared/services/beacon/client/std-http-client.go index 5c7de3931..a1abda1df 100644 --- a/shared/services/beacon/client/std-http-client.go +++ b/shared/services/beacon/client/std-http-client.go @@ -48,6 +48,7 @@ const ( RequestContentType = "application/json" RequestSyncStatusPath = "/eth/v1/node/syncing" + RequestNodeVersionPath = "/eth/v1/node/version" RequestEth2ConfigPath = "/eth/v1/config/spec" RequestEth2DepositContractMethod = "/eth/v1/config/deposit_contract" RequestGenesisPath = "/eth/v1/beacon/genesis" @@ -107,6 +108,17 @@ func (c *StandardHttpClient) GetSyncStatus() (beacon.SyncStatus, error) { } +func (c *StandardHttpClient) GetNodeVersion() (beacon.NodeVersion, error) { + nodeVersion, err := c.getNodeVersion() + if err != nil { + return beacon.NodeVersion{}, err + } + + return beacon.NodeVersion{ + Version: nodeVersion.Data.Version, + }, nil +} + // Get the eth2 config func (c *StandardHttpClient) GetEth2Config() (beacon.Eth2Config, error) { @@ -572,6 +584,21 @@ func (c *StandardHttpClient) getSyncStatus() (SyncStatusResponse, error) { return syncStatus, nil } +func (c *StandardHttpClient) getNodeVersion() (NodeVersionResponse, error) { + responseBody, status, err := c.getRequest(RequestNodeVersionPath) + if err != nil { + return NodeVersionResponse{}, fmt.Errorf("Could not get node sync status: %w", err) + } + if status != http.StatusOK { + return NodeVersionResponse{}, fmt.Errorf("Could not get node sync status: HTTP status %d; response body: '%s'", status, string(responseBody)) + } + var nodeVersion NodeVersionResponse + if err := json.Unmarshal(responseBody, &nodeVersion); err != nil { + return NodeVersionResponse{}, fmt.Errorf("Could not decode node sync status: %w", err) + } + return nodeVersion, nil +} + // Get the eth2 config func (c *StandardHttpClient) getEth2Config() (Eth2ConfigResponse, error) { responseBody, status, err := c.getRequest(RequestEth2ConfigPath) diff --git a/shared/services/beacon/client/types.go b/shared/services/beacon/client/types.go index 5561b1d5c..1af74f28a 100644 --- a/shared/services/beacon/client/types.go +++ b/shared/services/beacon/client/types.go @@ -46,6 +46,13 @@ type SyncStatusResponse struct { SyncDistance uinteger `json:"sync_distance"` } `json:"data"` } + +type NodeVersionResponse struct { + Data struct { + Version string `json:"version"` + } `json:"data"` +} + type Eth2ConfigResponse struct { Data struct { SecondsPerSlot uinteger `json:"SECONDS_PER_SLOT"` diff --git a/shared/services/config/stadernode-config.go b/shared/services/config/stadernode-config.go index 4eab362a4..0b89421ba 100644 --- a/shared/services/config/stadernode-config.go +++ b/shared/services/config/stadernode-config.go @@ -324,6 +324,10 @@ func (cfg *StaderNodeConfig) GetMerkleProofApi() string { return cfg.baseStaderBackendUrl[cfg.Network.Value.(config.Network)] + "/merklesForElRewards/proofs/%s" } +func (cfg *StaderNodeConfig) GetNodeDiversityApi() string { + return cfg.baseStaderBackendUrl[cfg.Network.Value.(config.Network)] + "/saveNodeDiversity" +} + func (cfg *StaderNodeConfig) GetTxWatchUrl() string { return cfg.txWatchUrl[cfg.Network.Value.(config.Network)] } diff --git a/shared/services/ec-manager.go b/shared/services/ec-manager.go index 7e61a85c9..2f282bea4 100644 --- a/shared/services/ec-manager.go +++ b/shared/services/ec-manager.go @@ -21,7 +21,9 @@ package services import ( "context" + "encoding/json" "fmt" + "io" "math" "math/big" "strings" @@ -36,6 +38,7 @@ import ( "github.com/stader-labs/stader-node/shared/types/api" cfgtypes "github.com/stader-labs/stader-node/shared/types/config" "github.com/stader-labs/stader-node/shared/utils/log" + "github.com/stader-labs/stader-node/shared/utils/net" ) // This is a proxy for multiple ETH clients, providing natural fallback support if one of them fails. @@ -520,3 +523,51 @@ func (p *ExecutionClientManager) runFunction(function ecFunction) (interface{}, func (p *ExecutionClientManager) isDisconnected(err error) bool { return strings.Contains(err.Error(), "dial tcp") } + +func (p *ExecutionClientManager) Version() (string, error) { + if !p.primaryReady && !p.fallbackReady { + return "", fmt.Errorf("EC not ready") + } + + var url string + + if p.primaryReady { + url = p.primaryEcUrl + } else { + url = p.fallbackEcUrl + } + + payload := struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params []string `json:"params"` + Id int64 `json:"id"` + }{ + Jsonrpc: "2.0", + Method: "web3_clientVersion", + Params: []string{}, + Id: 1, + } + + res, err := net.MakePostRequest(url, payload) + if err != nil { + return "", err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + + response := struct { + Result string `json:"result"` + }{} + + err = json.Unmarshal(body, &response) + if err != nil { + return "", err + } + + return response.Result, nil +} diff --git a/shared/services/gas/gas.go b/shared/services/gas/gas.go index c1ffa08cf..53c5fc228 100644 --- a/shared/services/gas/gas.go +++ b/shared/services/gas/gas.go @@ -184,7 +184,7 @@ func handleEtherscanGasPrices(gasSuggestion etherscan.GasFeeSuggestion, priority desiredPriceFloat, err := strconv.ParseFloat(desiredPrice, 64) if err != nil { - fmt.Println("Not a valid gas price (%s), try again.", err.Error()) + fmt.Printf("Not a valid gas price (%s), try again.\n", err.Error()) continue } if desiredPriceFloat <= 0 { diff --git a/shared/services/requirements.go b/shared/services/requirements.go index 2e4bdaf34..7dfe8dda1 100644 --- a/shared/services/requirements.go +++ b/shared/services/requirements.go @@ -499,7 +499,7 @@ func waitBeaconClientSynced(c *cli.Context, verbose bool, timeout int64) (bool, // Check sync status if syncStatus.Syncing { if verbose { - log.Println("Eth 2.0 node syncing: %.2f%%\n", syncStatus.Progress*100) + log.Printf("Eth 2.0 node syncing: %.2f%%\n", syncStatus.Progress*100) } } else { return true, nil diff --git a/shared/services/stader/client.go b/shared/services/stader/client.go index 88b49cfab..b287e6e39 100644 --- a/shared/services/stader/client.go +++ b/shared/services/stader/client.go @@ -298,7 +298,7 @@ func (c *Client) UpdatePrometheusConfiguration(settings map[string]string) error } err = os.Chmod(prometheusConfigPath, 0664) if err != nil { - return fmt.Errorf("Could not set Prometheus config file permissions: %w", shellescape.Quote(prometheusConfigPath), err) + return fmt.Errorf("Could not set Prometheus config file permissions: %s: %w", shellescape.Quote(prometheusConfigPath), err) } return nil diff --git a/shared/services/wallet/node.go b/shared/services/wallet/node.go index 103b5314f..c4caf2c20 100644 --- a/shared/services/wallet/node.go +++ b/shared/services/wallet/node.go @@ -22,6 +22,7 @@ package wallet import ( "context" "crypto/ecdsa" + "encoding/hex" "errors" "fmt" "math/big" @@ -186,3 +187,29 @@ func (w *Wallet) getNodeDerivedKey(index uint) (*hdkeychain.ExtendedKey, string, return key, derivationPath, nil } + +// Get the node hex encoding public key +func (w *Wallet) GetNodePubkey() (string, error) { + + // Check wallet is initialized + if !w.IsInitialized() { + return "", errors.New("Wallet is not initialized") + } + + // Get private key + privateKey, _, err := w.getNodePrivateKey() + if err != nil { + return "", err + } + + // Get public key + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return "", errors.New("Could not get node public key") + } + + publickeyBytes := crypto.FromECDSAPub(publicKeyECDSA) + + return hex.EncodeToString(publickeyBytes), nil +} diff --git a/shared/types/stader-backend/node-diversity.go b/shared/types/stader-backend/node-diversity.go new file mode 100644 index 000000000..c7c082d73 --- /dev/null +++ b/shared/types/stader-backend/node-diversity.go @@ -0,0 +1,21 @@ +package stader_backend + +type NodeDiversityRequest struct { + Signature string `json:"signature"` + Message *NodeDiversity `json:"message"` +} + +type NodeDiversity struct { + ExecutionClient string `json:"executionClient"` + ConsensusClient string `json:"consensusClient"` + ValidatorClient string `json:"validatorClient"` + TotalNonTerminalKeys uint64 `json:"totalNonTerminalKeys"` + NodeAddress string `json:"nodeAddress"` + NodePublicKey string `json:"nodePublicKey"` + Relays string `json:"relays"` +} + +type NodeDiversityResponseType struct { + Success bool `json:"success"` + Error string `json:"error"` +} diff --git a/shared/utils/stader/node-diversity.go b/shared/utils/stader/node-diversity.go new file mode 100644 index 000000000..6902c4080 --- /dev/null +++ b/shared/utils/stader/node-diversity.go @@ -0,0 +1,37 @@ +package stader + +import ( + "encoding/json" + "fmt" + + "github.com/stader-labs/stader-node/shared/services" + stader_backend "github.com/stader-labs/stader-node/shared/types/stader-backend" + "github.com/stader-labs/stader-node/shared/utils/net" + "github.com/urfave/cli" +) + +func SendNodeDiversityResponseType( + c *cli.Context, + request *stader_backend.NodeDiversityRequest, +) (*stader_backend.NodeDiversityResponseType, error) { + config, err := services.GetConfig(c) + if err != nil { + return nil, err + } + + res, err := net.MakePostRequest(config.StaderNode.GetNodeDiversityApi(), request) + if err != nil { + return nil, fmt.Errorf("request to GetNodeDiversityApi %w", err) + } + defer res.Body.Close() + + var resp stader_backend.NodeDiversityResponseType + err = json.NewDecoder(res.Body).Decode(&resp) + + if err != nil { + return nil, fmt.Errorf("decode NodeDiversityResponseType %w", err) + } + + return &resp, nil + +} diff --git a/stader-cli/node/claim-sp-rewards.go b/stader-cli/node/claim-sp-rewards.go index 71ee4a401..18d290084 100644 --- a/stader-cli/node/claim-sp-rewards.go +++ b/stader-cli/node/claim-sp-rewards.go @@ -74,7 +74,7 @@ func ClaimSpRewards(c *cli.Context) error { fmt.Println("Following are the unclaimed cycles, Please enter in a comma separated string the cycles you want to claim rewards for:") - fmt.Printf("%-18s%-14.30s%-14.10s%-10s\n", "Cycle Number", "Cycle Date", "ETH Rewards", "SD Rewards") + fmt.Printf("\n%-18s%-14.30s%-14.10s%-10s\n", "Cycle Number", "Cycle Date", "ETH Rewards", "SD Rewards") cyclesToClaim := map[int64]bool{} for { for _, cycleInfo := range detailedCyclesInfo.DetailedCyclesInfo { diff --git a/stader-cli/service/service.go b/stader-cli/service/service.go index dc90c7740..867a56788 100644 --- a/stader-cli/service/service.go +++ b/stader-cli/service/service.go @@ -684,8 +684,8 @@ func startService( fmt.Printf("%sWarning: couldn't verify that the validator container can be safely restarted:\n\t%s\n", colorYellow, err.Error()) fmt.Println("If you are changing to a different ETH2 client, it may resubmit an attestation you have already submitted.") fmt.Println("This will slash your validator!") - fmt.Println("To prevent slashing, you must wait 15 minutes from the time you stopped the clients before starting them again.\n") - fmt.Println("**If you did NOT change clients, you can safely ignore this warning.**\n") + fmt.Println("To prevent slashing, you must wait 15 minutes from the time you stopped the clients before starting them again.") + fmt.Println("**If you did NOT change clients, you can safely ignore this warning.**") if !cliutils.Confirm(fmt.Sprintf("Press y when you understand the above warning, have waited, and are ready to start Stader:%s", colorReset)) { fmt.Println("Cancelled.") return nil @@ -914,7 +914,7 @@ func pruneExecutionClient(c *cli.Context) error { } fmt.Println("This will shut down your main execution client and prune its database, freeing up disk space.") - fmt.Println("Once pruning is complete, your execution client will restart automatically.\n") + fmt.Println("Once pruning is complete, your execution client will restart automatically.") if selectedEc == cfgtypes.ExecutionClient_Geth { if cfg.UseFallbackClients.Value == false { @@ -1488,7 +1488,7 @@ func exportEcData(c *cli.Context, targetDir string) error { fmt.Println("This will export your execution client's chain data to an external directory, such as a portable hard drive.") fmt.Println("If your execution client is running, it will be shut down.") - fmt.Println("Once the export is complete, your execution client will restart automatically.\n") + fmt.Println("Once the export is complete, your execution client will restart automatically.") // Get the container prefix prefix, err := getContainerPrefix(staderClient) @@ -1606,7 +1606,7 @@ func importEcData(c *cli.Context, sourceDir string) error { fmt.Println("This will import execution layer chain data that you previously exported into your execution client.") fmt.Println("If your execution client is running, it will be shut down.") - fmt.Println("Once the import is complete, your execution client will restart automatically.\n") + fmt.Println("Once the import is complete, your execution client will restart automatically.") // Get the volume to import into executionContainerName := prefix + ExecutionContainerSuffix diff --git a/stader-cli/wallet/export.go b/stader-cli/wallet/export.go index 1354b25d3..d846d5e7e 100644 --- a/stader-cli/wallet/export.go +++ b/stader-cli/wallet/export.go @@ -51,8 +51,8 @@ func exportWallet(c *cli.Context) error { // Check if stdout is interactive stat, err := os.Stdout.Stat() if err != nil { - fmt.Fprintf(os.Stderr, "An error occured while determining whether or not the output is a tty: %w\n"+ - "Use \"stader-cli --secure-session wallet export\" to bypass.\n", err) + fmt.Fprintf(os.Stderr, "An error occured while determining whether or not the output is a tty: %s\n"+ + "Use \"stader-cli --secure-session wallet export\" to bypass.\n", err.Error()) os.Exit(1) } diff --git a/stader/node/node.go b/stader/node/node.go index 77184a445..2d2f72de3 100644 --- a/stader/node/node.go +++ b/stader/node/node.go @@ -20,16 +20,23 @@ along with this program. If not, see . package node import ( + "crypto/ecdsa" _ "embed" + "encoding/hex" + "encoding/json" "fmt" "io/ioutil" "net/http" "os" "path/filepath" "strconv" + "strings" "sync" "time" + "github.com/ethereum/go-ethereum/accounts" + eCryto "github.com/ethereum/go-ethereum/crypto" + cfgtypes "github.com/stader-labs/stader-node/shared/types/config" stader_backend "github.com/stader-labs/stader-node/shared/types/stader-backend" "github.com/stader-labs/stader-node/shared/utils/crypto" @@ -38,6 +45,7 @@ import ( "github.com/stader-labs/stader-node/shared/utils/stdr" "github.com/stader-labs/stader-node/shared/utils/validator" "github.com/stader-labs/stader-node/stader-lib/node" + stader_lib "github.com/stader-labs/stader-node/stader-lib/stader" eth2types "github.com/wealdtech/go-eth2-types/v2" "github.com/fatih/color" @@ -45,6 +53,7 @@ import ( "github.com/stader-labs/stader-node/shared/services" "github.com/stader-labs/stader-node/shared/services/config" + "github.com/stader-labs/stader-node/shared/services/wallet" "github.com/stader-labs/stader-node/shared/utils/log" ) @@ -53,6 +62,8 @@ var preSignedCooldown, _ = time.ParseDuration("1h") var feeRecepientPollingInterval, _ = time.ParseDuration("5m") var taskCooldown, _ = time.ParseDuration("10s") var merkleProofsDownloadInterval, _ = time.ParseDuration("3h") +var nodeDiversityTracker, _ = time.ParseDuration("24h") +var nodeDiversityTrackerCooldown, _ = time.ParseDuration("10m") const ( MaxConcurrentEth1Requests = 200 @@ -105,6 +116,11 @@ func run(c *cli.Context) error { return err } + ec, err := services.GetEthClient(c) + if err != nil { + return err + } + bc, err := services.GetBeaconClient(c) if err != nil { return err @@ -135,7 +151,7 @@ func run(c *cli.Context) error { // Wait group to handle the various threads wg := new(sync.WaitGroup) - wg.Add(3) + wg.Add(4) // validator presigned loop go func() { @@ -385,12 +401,191 @@ func run(c *cli.Context) error { wg.Done() }() + go func() { + defer wg.Done() + + for { + + infoLog.Println("Start checking node diversity metrics") + // Check the EC status + err := services.WaitEthClientSynced(c, false) // Force refresh the primary / fallback EC status + if err != nil { + errorLog.Println(err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + // Check the BC status + err = services.WaitBeaconClientSynced(c, false) // Force refresh the primary / fallback BC status + if err != nil { + errorLog.Println(err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + privateKey, err := w.GetNodePrivateKey() + if err != nil { + errorLog.Printlnf("Error GetNodePrivateKey %+v", err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + cfg, err := services.GetConfig(c) + if err != nil { + errorLog.Printlnf("Error getconfig %+v", err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + infoLog.Printlnf("Running the node diversity tracker daemon") + + message, err := makeNodeDiversityMessage(ec, bc, pnr, w, cfg) + if err != nil { + errorLog.Printlnf("Error makesNodeDiversityMessage %+v", err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + request, err := makeNodeDiversityRequest(message, privateKey) + if err != nil { + errorLog.Printlnf("Error makesNodeDiversityRequest %+v", err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + response, err := stader.SendNodeDiversityResponseType(c, request) + + if err != nil { + errorLog.Printlnf("Error SendNodeDiversityResponseType %+v", err) + time.Sleep(nodeDiversityTrackerCooldown) + + continue + } + + if response.Success { + infoLog.Println("Successfully sent the NodeDiversity message") + } else { + errorLog.Println("Failed to send the NodeDiversity message with err: %s\n", response.Error) + } + + infoLog.Println("Done checking node diversity metrics") + time.Sleep(nodeDiversityTracker) + } + }() + // Wait for both threads to stop wg.Wait() return nil } +func makeNodeDiversityMessage( + ec *services.ExecutionClientManager, + bc *services.BeaconClientManager, + pnr *stader_lib.PermissionlessNodeRegistryContractManager, + w *wallet.Wallet, + cfg *config.StaderConfig, +) (*stader_backend.NodeDiversity, error) { + bcNodeVersion, err := bc.GetNodeVersion() + if err != nil { + return nil, err + } + + ecVersion, err := ec.Version() + if err != nil { + return nil, err + } + + nodePublicKey, err := w.GetNodePubkey() + if err != nil { + return nil, err + } + + nodeAccount, err := w.GetNodeAccount() + if err != nil { + return nil, err + } + + var relayString string + + if cfg.EnableMevBoost.Value == true { + var relays []cfgtypes.MevRelay + + relayNames := []string{} + switch cfg.MevBoost.Mode.Value.(cfgtypes.Mode) { + case cfgtypes.Mode_Local: + relays = cfg.MevBoost.GetEnabledMevRelays() + + for _, relay := range relays { + relayNames = append(relayNames, string(relay.ID)) + } + } + + relayString = strings.Join(relayNames, ",") + } + + operatorID, err := node.GetOperatorId(pnr, nodeAccount.Address, nil) + if err != nil { + return nil, err + } + + totalValidatorKeys, err := node.GetTotalValidatorKeys(pnr, operatorID, nil) + if err != nil { + return nil, err + } + + //fmt.Printf("Get total non terminal validator keys\n") + totalNonTerminalValidatorKeys, err := node.GetTotalNonTerminalValidatorKeys(pnr, nodeAccount.Address, totalValidatorKeys, nil) + if err != nil { + return nil, err + } + + // Get the new validator client according to the settings file + selectedConsensusClientConfig, err := cfg.GetSelectedConsensusClientConfig() + if err != nil { + return nil, err + } + + message := stader_backend.NodeDiversity{ + ExecutionClient: ecVersion, + ConsensusClient: bcNodeVersion.Version, + ValidatorClient: selectedConsensusClientConfig.GetName(), + NodeAddress: nodeAccount.Address.String(), + TotalNonTerminalKeys: totalNonTerminalValidatorKeys, + NodePublicKey: nodePublicKey, + Relays: relayString, + } + + return &message, nil +} + +func makeNodeDiversityRequest(msg *stader_backend.NodeDiversity, privateKey *ecdsa.PrivateKey) (*stader_backend.NodeDiversityRequest, error) { + msgBytes, err := json.Marshal(msg) + if err != nil { + return nil, err + } + + msgHashed := accounts.TextHash(msgBytes) + + signedMessage, err := eCryto.Sign(msgHashed, privateKey) + if err != nil { + return nil, err + } + + request := stader_backend.NodeDiversityRequest{ + Signature: hex.EncodeToString(signedMessage[:64]), + Message: msg, + } + + return &request, nil +} + // Configure HTTP transport settings func configureHTTP() { diff --git a/stader/node/node_test.go b/stader/node/node_test.go new file mode 100644 index 000000000..333c402cb --- /dev/null +++ b/stader/node/node_test.go @@ -0,0 +1,143 @@ +package node + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/crypto" + stader_backend "github.com/stader-labs/stader-node/shared/types/stader-backend" +) + +var ( + ExecutionClient = "besu/v1.25.2+78c7bf5f/linux-x64/dotnet8.0.1" + ConsensusClient = "teku/v4.6.0-rc.0-2e8e160/x86_64-linux" + ValidatorClient = "prysm" + + PrvFake = "f7d400ec4062274059f531413e03a938fd837e3a07692338ab78dfd93d1e21e1" +) + +func TestVerifySignature(t *testing.T) { + privateKey, err := crypto.HexToECDSA(PrvFake) + if err != nil { + t.Error(err) + } + + publicKey := privateKey.Public() + + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + t.Error(err) + } + + pubkeyBytes := crypto.FromECDSAPub(publicKeyECDSA) + + req, err := makeNodeDiversityRequest(&stader_backend.NodeDiversity{ + ExecutionClient: ExecutionClient, + ConsensusClient: ConsensusClient, + ValidatorClient: ValidatorClient, + TotalNonTerminalKeys: 10, + NodeAddress: crypto.PubkeyToAddress(*publicKeyECDSA).String(), + NodePublicKey: hex.EncodeToString(pubkeyBytes), + Relays: "ultrasound,aestus", + }, privateKey) + if err != nil { + t.Error(err) + } + + msgStr := req.Message + verified := verifySignature(t, msgStr, req.Signature) + + if !verified { + t.Error("verified should success") + } +} + +func TestVerifySignatureFailed(t *testing.T) { + privateKey, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + t.Error(err) + } + + publicKey := privateKey.Public() + + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + t.Error(err) + } + + publickeyBytes := crypto.FromECDSAPub(publicKeyECDSA) + + privateKeyFake, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + t.Error(err) + } + + msg := stader_backend.NodeDiversity{ + ExecutionClient: ExecutionClient, + ConsensusClient: ConsensusClient, + ValidatorClient: ValidatorClient, + TotalNonTerminalKeys: 10, + Relays: "ultrasound,aestus", + NodeAddress: crypto.PubkeyToAddress(*publicKeyECDSA).String(), + NodePublicKey: hex.EncodeToString(publickeyBytes), + } + + req, err := makeNodeDiversityRequest(&msg, privateKeyFake) + if err != nil { + t.Error(err) + } + + verified := verifySignature(t, req.Message, req.Signature) + + if verified { + t.Error("verified should failed") + } +} + +func verifySignature(t *testing.T, msg *stader_backend.NodeDiversity, signEncoded string) bool { + t.Helper() + + signRaw, err := hex.DecodeString(signEncoded) + if err != nil { + t.Error(err) + } + + if msg.ConsensusClient != ConsensusClient { + t.Error(err) + } + + if msg.ExecutionClient != ExecutionClient { + t.Error(err) + } + + if msg.ValidatorClient != ValidatorClient { + t.Error(err) + } + + if msg.TotalNonTerminalKeys != 10 { + t.Error(err) + } + + msgBytes, err := json.Marshal(msg) + if err != nil { + t.Error(err) + } + + fmt.Printf("[%s]", msgBytes) + fmt.Printf("[%s]", signEncoded) + + msgHashed := accounts.TextHash(msgBytes) + + decodePubkey, err := hex.DecodeString(msg.NodePublicKey) + if err != nil { + t.Error(err) + } + + // 4. Verify + return crypto.VerifySignature(decodePubkey, msgHashed, signRaw) +}