Skip to content

Commit 514c667

Browse files
committed
Add voting power flag, file to treegen output
1 parent a92de2f commit 514c667

3 files changed

Lines changed: 156 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
@@ -167,17 +168,18 @@ func GenerateTree(c *cli.Context) error {
167168

168169
// Create the generator
169170
generator := treeGenerator{
170-
log: &logger,
171-
errLog: &errLogger,
172-
rp: rp,
173-
cfg: cfg,
174-
bn: bn,
175-
mgr: mgr,
176-
beaconConfig: beaconConfig,
177-
outputDir: c.String("output-dir"),
178-
prettyPrint: c.Bool("pretty-print"),
179-
ruleset: c.Uint64("ruleset"),
180-
useRollingRecords: c.Bool("use-rolling-records"),
171+
log: &logger,
172+
errLog: &errLogger,
173+
rp: rp,
174+
cfg: cfg,
175+
bn: bn,
176+
mgr: mgr,
177+
beaconConfig: beaconConfig,
178+
outputDir: c.String("output-dir"),
179+
prettyPrint: c.Bool("pretty-print"),
180+
ruleset: c.Uint64("ruleset"),
181+
useRollingRecords: c.Bool("use-rolling-records"),
182+
generateVotingPower: c.Bool("generate-voting-power"),
181183
}
182184

183185
// initialize the generator targets
@@ -414,6 +416,14 @@ func (g *treeGenerator) generateRewardsFile(treegen *rprewards.TreeGenerator) (r
414416
return treegen.GenerateTreeWithRuleset(g.ruleset)
415417
}
416418

419+
func (g *treeGenerator) serializeVotingPower(votingPowerFile *VotingPowerFile) ([]byte, error) {
420+
if g.prettyPrint {
421+
return json.MarshalIndent(votingPowerFile, "", "\t")
422+
}
423+
424+
return json.Marshal(votingPowerFile)
425+
}
426+
417427
// Serializes the minipool performance file into JSON
418428
func (g *treeGenerator) serializeMinipoolPerformance(rewardsFile rprewards.IRewardsFile) ([]byte, error) {
419429
perfFile := rewardsFile.GetMinipoolPerformanceFile()
@@ -435,7 +445,7 @@ func (g *treeGenerator) serializeRewardsTree(rewardsFile rprewards.IRewardsFile)
435445
}
436446

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

@@ -472,6 +482,21 @@ func (g *treeGenerator) writeFiles(rewardsFile rprewards.IRewardsFile) error {
472482
}
473483

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

477502
return nil
@@ -622,6 +647,7 @@ func (g *treeGenerator) approximateRethSpRewards() error {
622647

623648
// Generate a complete rewards tree
624649
func (g *treeGenerator) generateTree() error {
650+
var votingPowerFile *VotingPowerFile
625651
args, err := g.getTreegenArgs()
626652
if err != nil {
627653
return fmt.Errorf("error compiling treegen arguments: %w", err)
@@ -633,6 +659,12 @@ func (g *treeGenerator) generateTree() error {
633659
return err
634660
}
635661

662+
// If a voting power file was requested, generate it now.
663+
if g.generateVotingPower {
664+
votingPowerFile = g.GenerateVotingPower(args.state)
665+
votingPowerFile.Time = args.endTime
666+
}
667+
636668
// Generate the rewards file
637669
start := time.Now()
638670
rewardsFile, err := g.generateRewardsFile(treegen)
@@ -656,7 +688,7 @@ func (g *treeGenerator) generateTree() error {
656688
}
657689
}
658690

659-
err = g.writeFiles(rewardsFile)
691+
err = g.writeFiles(rewardsFile, votingPowerFile)
660692
if err != nil {
661693
return err
662694
}

voting-power.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 and divide by 2
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+
votingPower.Rsh(votingPower, 1)
56+
return votingPower
57+
58+
}
59+
60+
func (g *treeGenerator) GenerateVotingPower(s *state.NetworkState) *VotingPowerFile {
61+
out := new(VotingPowerFile)
62+
63+
out.Network = string(g.cfg.Smartnode.Network.Value.(cfgtypes.Network))
64+
out.ConsensusSlot = s.BeaconSlotNumber
65+
out.ExecutionBlock = s.ElBlockNumber
66+
out.TotalPower = rewards.NewQuotedBigInt(0)
67+
out.NodePower = make(map[common.Address]*rewards.QuotedBigInt, len(s.NodeDetails))
68+
for _, node := range s.NodeDetails {
69+
activeMinipoolCount := int64(0)
70+
for _, mpd := range s.MinipoolDetailsByNode[node.NodeAddress] {
71+
// Ignore finalised
72+
if mpd.Finalised {
73+
continue
74+
}
75+
76+
activeMinipoolCount += 1
77+
}
78+
79+
// Get provided ETH (32 * minipoolCount - matched)
80+
ethProvided := big.NewInt(activeMinipoolCount * 32)
81+
ethProvided.Mul(ethProvided, oneEth)
82+
ethProvided.Sub(ethProvided, node.EthMatched)
83+
84+
// Calculate the Voting Power
85+
nodeVotingPower := rewards.NewQuotedBigInt(0)
86+
nodeVotingPower.Set(getNodeVotingPower(s, ethProvided, node.RplStake))
87+
out.TotalPower.Add(&out.TotalPower.Int, &nodeVotingPower.Int)
88+
out.NodePower[node.NodeAddress] = nodeVotingPower
89+
}
90+
91+
return out
92+
}

0 commit comments

Comments
 (0)