Skip to content

Commit f81a4ec

Browse files
authored
Merge pull request #9 from jshufro/jms/generate-voting-power
Add voting power flag, file to treegen output
2 parents 4ad2fd6 + 3d8ea90 commit f81a4ec

3 files changed

Lines changed: 155 additions & 26 deletions

File tree

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ func main() {
9595
Usage: "Enable the rolling record capability of the Smartnode tree generator. Use this to store and load record caches instead of recalculating attestation performance each time you run treegen.",
9696
Value: false,
9797
},
98+
&cli.BoolFlag{
99+
Name: "generate-voting-power",
100+
Aliases: []string{"gvp"},
101+
Usage: "If Enabled, a file containing the voting power breakdown of all nodes will be saved to the output directory.",
102+
Value: false,
103+
},
98104
&cli.StringFlag{
99105
Name: "cpuprofile",
100106
Aliases: []string{"c"},

tree-gen.go

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,20 @@ type treegenArguments struct {
8787

8888
// Treegen holder for the requested execution metadata and necessary artifacts
8989
type treeGenerator struct {
90-
log *log.ColorLogger
91-
errLog *log.ColorLogger
92-
rp *rocketpool.RocketPool
93-
cfg *config.RocketPoolConfig
94-
mgr *state.NetworkStateManager
95-
recordMgr *rprewards.RollingRecordManager
96-
bn beacon.Client
97-
beaconConfig beacon.Eth2Config
98-
targets targets
99-
outputDir string
100-
prettyPrint bool
101-
ruleset uint64
102-
useRollingRecords bool
90+
log *log.ColorLogger
91+
errLog *log.ColorLogger
92+
rp *rocketpool.RocketPool
93+
cfg *config.RocketPoolConfig
94+
mgr *state.NetworkStateManager
95+
recordMgr *rprewards.RollingRecordManager
96+
bn beacon.Client
97+
beaconConfig beacon.Eth2Config
98+
targets targets
99+
outputDir string
100+
prettyPrint bool
101+
ruleset uint64
102+
useRollingRecords bool
103+
generateVotingPower bool
103104
}
104105

105106
// Generates a new rewards tree based on the command line flags
@@ -170,17 +171,18 @@ func GenerateTree(c *cli.Context) error {
170171

171172
// Create the generator
172173
generator := treeGenerator{
173-
log: &logger,
174-
errLog: &errLogger,
175-
rp: rp,
176-
cfg: cfg,
177-
bn: bn,
178-
mgr: mgr,
179-
beaconConfig: beaconConfig,
180-
outputDir: c.String("output-dir"),
181-
prettyPrint: c.Bool("pretty-print"),
182-
ruleset: c.Uint64("ruleset"),
183-
useRollingRecords: c.Bool("use-rolling-records"),
174+
log: &logger,
175+
errLog: &errLogger,
176+
rp: rp,
177+
cfg: cfg,
178+
bn: bn,
179+
mgr: mgr,
180+
beaconConfig: beaconConfig,
181+
outputDir: c.String("output-dir"),
182+
prettyPrint: c.Bool("pretty-print"),
183+
ruleset: c.Uint64("ruleset"),
184+
useRollingRecords: c.Bool("use-rolling-records"),
185+
generateVotingPower: c.Bool("generate-voting-power"),
184186
}
185187

186188
// initialize the generator targets
@@ -417,6 +419,14 @@ func (g *treeGenerator) generateRewardsFile(treegen *rprewards.TreeGenerator) (r
417419
return treegen.GenerateTreeWithRuleset(g.ruleset)
418420
}
419421

422+
func (g *treeGenerator) serializeVotingPower(votingPowerFile *VotingPowerFile) ([]byte, error) {
423+
if g.prettyPrint {
424+
return json.MarshalIndent(votingPowerFile, "", "\t")
425+
}
426+
427+
return json.Marshal(votingPowerFile)
428+
}
429+
420430
// Serializes the minipool performance file into JSON
421431
func (g *treeGenerator) serializeMinipoolPerformance(rewardsFile rprewards.IRewardsFile) ([]byte, error) {
422432
perfFile := rewardsFile.GetMinipoolPerformanceFile()
@@ -438,7 +448,7 @@ func (g *treeGenerator) serializeRewardsTree(rewardsFile rprewards.IRewardsFile)
438448
}
439449

440450
// Writes both the performance file and the rewards file to disk
441-
func (g *treeGenerator) writeFiles(rewardsFile rprewards.IRewardsFile) error {
451+
func (g *treeGenerator) writeFiles(rewardsFile rprewards.IRewardsFile, votingPowerFile *VotingPowerFile) error {
442452
g.log.Printlnf("Saving JSON files...")
443453
index := rewardsFile.GetHeader().Index
444454

@@ -475,6 +485,21 @@ func (g *treeGenerator) writeFiles(rewardsFile rprewards.IRewardsFile) error {
475485
}
476486

477487
g.log.Printlnf("Saved rewards snapshot file to %s", rewardsTreePath)
488+
489+
// Write the voting power file to disk
490+
if votingPowerFile != nil {
491+
votingPowerFileBytes, err := g.serializeVotingPower(votingPowerFile)
492+
if err != nil {
493+
return fmt.Errorf("error serializing voting power file into JSON: %w", err)
494+
}
495+
496+
votingFilePath := filepath.Join(g.outputDir, fmt.Sprintf("rp-voting-power-%s-%d.json", string(g.cfg.Smartnode.Network.Value.(cfgtypes.Network)), votingPowerFile.ConsensusSlot))
497+
err = os.WriteFile(votingFilePath, votingPowerFileBytes, 0644)
498+
if err != nil {
499+
return fmt.Errorf("error saving voting power file to %s: %w", votingFilePath, err)
500+
}
501+
g.log.Printlnf("Saved voting power file to %s", votingFilePath)
502+
}
478503
g.log.Printlnf("Successfully generated rewards snapshot for interval %d", index)
479504

480505
return nil
@@ -625,6 +650,7 @@ func (g *treeGenerator) approximateRethSpRewards() error {
625650

626651
// Generate a complete rewards tree
627652
func (g *treeGenerator) generateTree() error {
653+
var votingPowerFile *VotingPowerFile
628654
args, err := g.getTreegenArgs()
629655
if err != nil {
630656
return fmt.Errorf("error compiling treegen arguments: %w", err)
@@ -636,6 +662,12 @@ func (g *treeGenerator) generateTree() error {
636662
return err
637663
}
638664

665+
// If a voting power file was requested, generate it now.
666+
if g.generateVotingPower {
667+
votingPowerFile = g.GenerateVotingPower(args.state)
668+
votingPowerFile.Time = args.endTime
669+
}
670+
639671
// Generate the rewards file
640672
start := time.Now()
641673
rewardsFile, err := g.generateRewardsFile(treegen)
@@ -659,7 +691,7 @@ func (g *treeGenerator) generateTree() error {
659691
}
660692
}
661693

662-
err = g.writeFiles(rewardsFile)
694+
err = g.writeFiles(rewardsFile, votingPowerFile)
663695
if err != nil {
664696
return err
665697
}

voting-power.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package main
2+
3+
import (
4+
"math/big"
5+
"time"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/rocket-pool/smartnode/shared/services/rewards"
9+
"github.com/rocket-pool/smartnode/shared/services/state"
10+
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config"
11+
)
12+
13+
var (
14+
oneEth = big.NewInt(1e18)
15+
_1_5_Eth = big.NewInt(15e17)
16+
)
17+
18+
type VotingPowerFile struct {
19+
Network string `json:"network"`
20+
Time time.Time `json:"time"`
21+
ConsensusSlot uint64 `json:"consensusSlot"`
22+
ExecutionBlock uint64 `json:"executionBlock"`
23+
TotalPower *rewards.QuotedBigInt `json:"totalPower"`
24+
NodePower map[common.Address]*rewards.QuotedBigInt `json:"nodePower"`
25+
}
26+
27+
func getNodeVotingPower(s *state.NetworkState, ethProvided *big.Int, nodeStake *big.Int) *big.Int {
28+
rplPrice := s.NetworkDetails.RplPrice
29+
30+
// No RPL staked means no voting power
31+
if nodeStake.Sign() == 0 {
32+
return big.NewInt(0)
33+
}
34+
35+
// First calculate the maximum rpl that can be used as input
36+
// maxVotingRpl := (eligibleBondedEth * 1.5 Eth / RplPrice)
37+
maxVotingRpl := big.NewInt(0)
38+
maxVotingRpl.Mul(ethProvided, _1_5_Eth)
39+
maxVotingRpl.Quo(maxVotingRpl, rplPrice)
40+
41+
// Determine the voting RPL
42+
// votingRpl := min(maxVotingRpl, nodeStake)
43+
var votingRpl *big.Int
44+
if maxVotingRpl.Cmp(nodeStake) <= 0 {
45+
votingRpl = maxVotingRpl
46+
} else {
47+
votingRpl = nodeStake
48+
}
49+
50+
// Now take the square root
51+
// Because the units are in wei, we need to multiply votingRpl by 1 Eth before square rooting.
52+
votingPower := big.NewInt(0)
53+
votingPower.Mul(votingRpl, oneEth)
54+
votingPower.Sqrt(votingPower)
55+
return votingPower
56+
57+
}
58+
59+
func (g *treeGenerator) GenerateVotingPower(s *state.NetworkState) *VotingPowerFile {
60+
out := new(VotingPowerFile)
61+
62+
out.Network = string(g.cfg.Smartnode.Network.Value.(cfgtypes.Network))
63+
out.ConsensusSlot = s.BeaconSlotNumber
64+
out.ExecutionBlock = s.ElBlockNumber
65+
out.TotalPower = rewards.NewQuotedBigInt(0)
66+
out.NodePower = make(map[common.Address]*rewards.QuotedBigInt, len(s.NodeDetails))
67+
for _, node := range s.NodeDetails {
68+
activeMinipoolCount := int64(0)
69+
for _, mpd := range s.MinipoolDetailsByNode[node.NodeAddress] {
70+
// Ignore finalised
71+
if mpd.Finalised {
72+
continue
73+
}
74+
75+
activeMinipoolCount += 1
76+
}
77+
78+
// Get provided ETH (32 * minipoolCount - matched)
79+
ethProvided := big.NewInt(activeMinipoolCount * 32)
80+
ethProvided.Mul(ethProvided, oneEth)
81+
ethProvided.Sub(ethProvided, node.EthMatched)
82+
83+
// Calculate the Voting Power
84+
nodeVotingPower := rewards.NewQuotedBigInt(0)
85+
nodeVotingPower.Set(getNodeVotingPower(s, ethProvided, node.RplStake))
86+
out.TotalPower.Add(&out.TotalPower.Int, &nodeVotingPower.Int)
87+
out.NodePower[node.NodeAddress] = nodeVotingPower
88+
}
89+
90+
return out
91+
}

0 commit comments

Comments
 (0)