From c2e5a837e6d429a8a49e1a0f5fa22765b45bb0d8 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 21 May 2021 13:03:45 +0200 Subject: [PATCH] Adjust various CLI display ratios to arbitrary precision Originally the deviations from using float64 were insignificant, but at exabyte scale they start to show up. Cleanup all displays, and clarify the expectation text, adding an extra 99.9% probability calculator to `lotus-miner info` --- chain/types/bigint.go | 5 ++ cli/state.go | 26 ++++++--- cmd/lotus-shed/sync.go | 7 ++- cmd/lotus-storage-miner/info.go | 70 ++++++++++++++++++------ cmd/lotus-storage-miner/proving.go | 2 +- testplans/lotus-soup/rfwp/chain_state.go | 62 ++++++++++++++++----- 6 files changed, 132 insertions(+), 40 deletions(-) diff --git a/chain/types/bigint.go b/chain/types/bigint.go index da4857d5b..72ef52128 100644 --- a/chain/types/bigint.go +++ b/chain/types/bigint.go @@ -47,6 +47,11 @@ func BigDiv(a, b BigInt) BigInt { return BigInt{Int: big.NewInt(0).Div(a.Int, b.Int)} } +func BigDivFloat(num, den BigInt) float64 { + res, _ := new(big.Rat).SetFrac(num.Int, den.Int).Float64() + return res +} + func BigMod(a, b BigInt) BigInt { return BigInt{Int: big.NewInt(0).Mod(a.Int, b.Int)} } diff --git a/cli/state.go b/cli/state.go index 63e923485..05d70e29a 100644 --- a/cli/state.go +++ b/cli/state.go @@ -183,18 +183,23 @@ var StateMinerInfo = &cli.Command{ return err } - rpercI := types.BigDiv(types.BigMul(pow.MinerPower.RawBytePower, types.NewInt(1000000)), pow.TotalPower.RawBytePower) - qpercI := types.BigDiv(types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(1000000)), pow.TotalPower.QualityAdjPower) - fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n", color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), types.SizeStr(pow.TotalPower.RawBytePower), - float64(rpercI.Int64())/10000) + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n", color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), types.DeciStr(pow.TotalPower.QualityAdjPower), - float64(qpercI.Int64())/10000) + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) fmt.Println() @@ -302,8 +307,15 @@ var StatePowerCmd = &cli.Command{ tp := power.TotalPower if cctx.Args().Present() { mp := power.MinerPower - percI := types.BigDiv(types.BigMul(mp.QualityAdjPower, types.NewInt(1000000)), tp.QualityAdjPower) - fmt.Printf("%s(%s) / %s(%s) ~= %0.4f%%\n", mp.QualityAdjPower.String(), types.SizeStr(mp.QualityAdjPower), tp.QualityAdjPower.String(), types.SizeStr(tp.QualityAdjPower), float64(percI.Int64())/10000) + fmt.Printf( + "%s(%s) / %s(%s) ~= %0.4f%%\n", + mp.QualityAdjPower.String(), types.SizeStr(mp.QualityAdjPower), + tp.QualityAdjPower.String(), types.SizeStr(tp.QualityAdjPower), + types.BigDivFloat( + types.BigMul(mp.QualityAdjPower, big.NewInt(100)), + tp.QualityAdjPower, + ), + ) } else { fmt.Printf("%s(%s)\n", tp.QualityAdjPower.String(), types.SizeStr(tp.QualityAdjPower)) } diff --git a/cmd/lotus-shed/sync.go b/cmd/lotus-shed/sync.go index 65d2b6d6f..cab3bd29e 100644 --- a/cmd/lotus-shed/sync.go +++ b/cmd/lotus-shed/sync.go @@ -172,12 +172,13 @@ var syncScrapePowerCmd = &cli.Command{ return err } - qpercI := types.BigDiv(types.BigMul(totalWonPower.QualityAdjPower, types.NewInt(1000000)), totalPower.TotalPower.QualityAdjPower) - fmt.Println("Number of winning miners: ", len(miners)) fmt.Println("QAdjPower of winning miners: ", totalWonPower.QualityAdjPower) fmt.Println("QAdjPower of all miners: ", totalPower.TotalPower.QualityAdjPower) - fmt.Println("Percentage of winning QAdjPower: ", float64(qpercI.Int64())/10000) + fmt.Println("Percentage of winning QAdjPower: ", types.BigDivFloat( + types.BigMul(totalWonPower.QualityAdjPower, big.NewInt(100)), + totalPower.TotalPower.QualityAdjPower, + )) return nil }, diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 7650de035..9aac21420 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -3,6 +3,8 @@ package main import ( "context" "fmt" + "math" + corebig "math/big" "sort" "time" @@ -21,6 +23,7 @@ import ( "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" @@ -120,19 +123,23 @@ func infoCmdAct(cctx *cli.Context) error { return err } - rpercI := types.BigDiv(types.BigMul(pow.MinerPower.RawBytePower, types.NewInt(1000000)), pow.TotalPower.RawBytePower) - qpercI := types.BigDiv(types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(1000000)), pow.TotalPower.QualityAdjPower) - fmt.Printf("Power: %s / %s (%0.4f%%)\n", color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), types.DeciStr(pow.TotalPower.QualityAdjPower), - float64(qpercI.Int64())/10000) + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) fmt.Printf("\tRaw: %s / %s (%0.4f%%)\n", color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), types.SizeStr(pow.TotalPower.RawBytePower), - float64(rpercI.Int64())/10000) - + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) secCounts, err := api.StateMinerSectorCount(ctx, maddr, types.EmptyTSK) if err != nil { return err @@ -146,7 +153,7 @@ func infoCmdAct(cctx *cli.Context) error { } else { var faultyPercentage float64 if secCounts.Live != 0 { - faultyPercentage = float64(10000*nfaults/secCounts.Live) / 100. + faultyPercentage = float64(100*nfaults) / float64(secCounts.Live) } fmt.Printf("\tProving: %s (%s Faulty, %.2f%%)\n", types.SizeStr(types.BigMul(types.NewInt(proving), types.NewInt(uint64(mi.SectorSize)))), @@ -157,16 +164,47 @@ func infoCmdAct(cctx *cli.Context) error { if !pow.HasMinPower { fmt.Print("Below minimum power threshold, no blocks will be won") } else { - expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000 - if expWinChance > 0 { - if expWinChance > 1 { - expWinChance = 1 - } - winRate := time.Duration(float64(time.Second*time.Duration(build.BlockDelaySecs)) / expWinChance) - winPerDay := float64(time.Hour*24) / float64(winRate) - fmt.Print("Expected block win rate: ") - color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second)) + winRatio := new(corebig.Rat).Mul( + new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, + ), + // decrease the rate ever-so-slightly to very roughly account for the multi-win poisson distribution + // FIXME - this is not a scientifically derived number... like at all + corebig.NewRat(99997, 100000), + ) + + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Print("Projected average block win rate: ") + color.Blue( + "%.02f/week (every %s)", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) + + // Geometric distribution calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=c%3D99%3B+p%3D188809111007232%3B+n%3D5740343177447735296%3B+%281-%284.99*p%2Fn%29%29%5E%28t*2880%29%3D%281-%28c%2F100%29%29 + fmt.Print("Projected block win with ") + color.Green( + "99.9%% probability every %s", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Println("(projections DO NOT account for future network and miner growth)") } } diff --git a/cmd/lotus-storage-miner/proving.go b/cmd/lotus-storage-miner/proving.go index 66007b63d..0e36c6508 100644 --- a/cmd/lotus-storage-miner/proving.go +++ b/cmd/lotus-storage-miner/proving.go @@ -171,7 +171,7 @@ var provingInfoCmd = &cli.Command{ var faultPerc float64 if proving > 0 { - faultPerc = float64(faults*10000/proving) / 100 + faultPerc = float64(faults * 100 / proving) } fmt.Printf("Current Epoch: %d\n", cd.CurrentEpoch) diff --git a/testplans/lotus-soup/rfwp/chain_state.go b/testplans/lotus-soup/rfwp/chain_state.go index 90159e924..153e05d58 100644 --- a/testplans/lotus-soup/rfwp/chain_state.go +++ b/testplans/lotus-soup/rfwp/chain_state.go @@ -7,6 +7,8 @@ import ( "encoding/json" "fmt" "io" + "math" + corebig "math/big" "os" "sort" "text/tabwriter" @@ -27,6 +29,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" tstats "github.com/filecoin-project/lotus/tools/stats" ) @@ -581,18 +584,24 @@ func (i *MinerInfo) MarshalPlainText() ([]byte, error) { fmt.Fprintf(w, "Sector Size: %s\n", i.SectorSize) pow := i.MinerPower - rpercI := types.BigDiv(types.BigMul(pow.MinerPower.RawBytePower, types.NewInt(1000000)), pow.TotalPower.RawBytePower) - qpercI := types.BigDiv(types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(1000000)), pow.TotalPower.QualityAdjPower) fmt.Fprintf(w, "Byte Power: %s / %s (%0.4f%%)\n", types.SizeStr(pow.MinerPower.RawBytePower), types.SizeStr(pow.TotalPower.RawBytePower), - float64(rpercI.Int64())/10000) + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) fmt.Fprintf(w, "Actual Power: %s / %s (%0.4f%%)\n", types.DeciStr(pow.MinerPower.QualityAdjPower), types.DeciStr(pow.TotalPower.QualityAdjPower), - float64(qpercI.Int64())/10000) + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) fmt.Fprintf(w, "\tCommitted: %s\n", types.SizeStr(i.CommittedBytes)) @@ -608,16 +617,43 @@ func (i *MinerInfo) MarshalPlainText() ([]byte, error) { if !i.MinerPower.HasMinPower { fmt.Fprintf(w, "Below minimum power threshold, no blocks will be won\n") } else { - expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000 - if expWinChance > 0 { - if expWinChance > 1 { - expWinChance = 1 - } - winRate := time.Duration(float64(time.Second*time.Duration(build.BlockDelaySecs)) / expWinChance) - winPerDay := float64(time.Hour*24) / float64(winRate) - fmt.Fprintln(w, "Expected block win rate: ") - fmt.Fprintf(w, "%.4f/day (every %s)\n", winPerDay, winRate.Truncate(time.Second)) + winRatio := new(corebig.Rat).Mul( + new(corebig.Rat).SetFrac( + types.BigMul(pow.MinerPower.QualityAdjPower, types.NewInt(build.BlocksPerEpoch)).Int, + pow.TotalPower.QualityAdjPower.Int, + ), + // decrease the rate ever-so-slightly to very roughly account for the multi-win poisson distribution + // FIXME - this is not a scientifically derived number... like at all + corebig.NewRat(99997, 100000), + ) + + if winRatioFloat, _ := winRatio.Float64(); winRatioFloat > 0 { + + weekly, _ := new(corebig.Rat).Mul( + winRatio, + new(corebig.Rat).SetInt64(7*builtin.EpochsInDay), + ).Float64() + + avgDuration, _ := new(corebig.Rat).Mul( + new(corebig.Rat).SetInt64(builtin.EpochDurationSeconds), + new(corebig.Rat).Inv(winRatio), + ).Float64() + + fmt.Fprintf(w, "Projected average block win rate: %.02f/week (every %s)\n", + weekly, + (time.Second * time.Duration(avgDuration)).Truncate(time.Second).String(), + ) + + // Geometric distribution calculated as described in https://en.wikipedia.org/wiki/Geometric_distribution#Probability_Outcomes_Examples + // https://www.wolframalpha.com/input/?i=c%3D99%3B+p%3D188809111007232%3B+n%3D5740343177447735296%3B+%281-%284.99*p%2Fn%29%29%5E%28t*2880%29%3D%281-%28c%2F100%29%29 + fmt.Fprintf(w, "Projected block win with 99.9%% probability every %s\n", + (time.Second * time.Duration( + builtin.EpochDurationSeconds*math.Log(1-0.999)/ + math.Log(1-winRatioFloat), + )).Truncate(time.Second).String(), + ) + fmt.Fprintln(w, "(projections DO NOT account for future network and miner growth)") } }