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

Mina - correct calculation #59

Open
pavlamijic opened this issue Sep 20, 2024 · 0 comments
Open

Mina - correct calculation #59

pavlamijic opened this issue Sep 20, 2024 · 0 comments

Comments

@pavlamijic
Copy link

pavlamijic commented Sep 20, 2024

Hi there,

Obviously, the current calculation for Mina is incorrect because NC = Total active validators, as observed here:

image

image

I inspected the logic and causes of issues in calculation in mina.go and there are several which I addressed in the code below:

  1. Added totalStake Calculation:
    var totalStake float64 was added to accumulate the sum of all stake percentages.
  2. Adjusted Loop Exit Condition:
    Changed from if len(response.Content) == 0 to if len(response.Content) == 0 || pageNo >= response.TotalPages to ensure it stops correctly.
  3. Sorting Updated:
    Original sorting and reversing were replaced with a direct descending order sort using sort.Slice.
  4. Threshold Calculation Modified:
    Adjusted thresholdPercent to be dynamically calculated as totalStake * 0.50 to match the 50% of the total stake.
    Changes in calcNakamotoCoefficientForMina:
  5. Modified to take totalStake as a parameter and calculate the correct threshold dynamically.

I suggest you correct the mina.go code in the following way so that calculation is accurate:

package chains

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"sort"
	"time"
)

type MinaResponse struct {
	Content []struct {
		Pk             string  `json:"pk"`
		Name           string  `json:"name"`
		StakePercent   float64 `json:"stakePercent"`
		CanonicalBlock int     `json:"canonicalBlock"`
		SocialTelegram string  `json:"socialTelegram"`
	}
	TotalPages    int `json:"totalPages"`
	TotalElements int `json:"totalElements"`
}

type MinaErrorResponse struct {
	Id      int    `json:"id"`
	Jsonrpc string `json:"jsonrpc"`
	Error   string `json:"error"`
}

func reverse(numbers []float64) {
	for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 {
		numbers[i], numbers[j] = numbers[j], numbers[i]
	}
}

func Mina() (int, error) {
	var votingPowers []float64
	var totalStake float64 // Added: variable to track the total stake
	pageNo, entriesPerPage := 0, 50
	url := ""
	ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancelFunc()
	for true {
		// Check the most active url in the network logs here: https://mina.staketab.com/validators/stake
		// Sometimes it changes, like once it changed from mina.staketab.com to t-mina.staketab.com
		// Once, it was https://mina.staketab.com:8181/api/validator/all/
		url = fmt.Sprintf("https://minascan.io/mainnet/api/api/validators/?page=%d&size=%d&sortBy=amount_staked&type=active&findStr=&orderBy=DESC", pageNo, entriesPerPage)
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			log.Println(err)
			return 0, errors.New("create get request for mina")
		}

		resp, err := new(http.Client).Do(req)
		if err != nil {
			log.Println(err)
			return 0, errors.New("get request unsuccessful")
		}
		defer resp.Body.Close() // Moved this line to ensure the body is always closed, even if an error occurs

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return 0, err
		}

		var response MinaResponse
		err = json.Unmarshal(body, &response)
		if err != nil {
			return 0, err
		}

		// Original code:
		// if len(response.Content) == 0 {
		//     break
		// }

		// Updated condition: Break if no content or all pages have been fetched
		if len(response.Content) == 0 || pageNo >= response.TotalPages { 
			break
		}

		// loop through the validators voting powers
		for _, ele := range response.Content {
			votingPowers = append(votingPowers, ele.StakePercent)
			totalStake += ele.StakePercent // Added: Accumulate total stake of all validators
		}

		// increment counters
		pageNo += 1
	}

	// Original sorting and reversal:
	// sort.Float64s(votingPowers)
	// reverse(votingPowers)

	// Updated sorting: Sort voting powers in descending order
	sort.Slice(votingPowers, func(i, j int) bool {
		return votingPowers[i] > votingPowers[j]
	})

	// now we're ready to calculate the Nakamoto coefficient
	nakamotoCoefficient := calcNakamotoCoefficientForMina(votingPowers, totalStake) // Modified to pass totalStake
	fmt.Println("The Nakamoto coefficient for Mina is", nakamotoCoefficient)

	return nakamotoCoefficient, nil
}

func calcNakamotoCoefficientForMina(votingPowers []float64, totalStake float64) int { // Modified to accept totalStake
	// Original threshold calculation:
	// var cumulativePercent, thresholdPercent float64 = 0.00, 50.00

	// Updated to calculate the actual 50% threshold based on total stake
	var cumulativePercent float64 = 0.00
	thresholdPercent := totalStake * 0.50 // Calculate 50% threshold dynamically based on total stake
	nakamotoCoefficient := 0

	for _, vpp := range votingPowers {
		// since this is the actual voting percentage, no need to multiply with 100
		cumulativePercent += vpp
		nakamotoCoefficient += 1
		// Check if cumulative voting power exceeds 50% of total stake
		if cumulativePercent >= thresholdPercent { 
			break
		}
	}
	return nakamotoCoefficient
}

Local test of calculation using the above code:

image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant