Skip to content

Commit 06c1e4f

Browse files
committed
Merge branch 'main' into jms/v10
2 parents c586733 + f81a4ec commit 06c1e4f

3 files changed

Lines changed: 154 additions & 27 deletions

File tree

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ func main() {
9696
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.",
9797
Value: false,
9898
},
99+
&cli.BoolFlag{
100+
Name: "generate-voting-power",
101+
Aliases: []string{"gvp"},
102+
Usage: "If Enabled, a file containing the voting power breakdown of all nodes will be saved to the output directory.",
103+
Value: false,
104+
},
99105
&cli.StringFlag{
100106
Name: "cpuprofile",
101107
Aliases: []string{"c"},

tree-gen.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,19 @@ type treegenArguments struct {
8888

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

106106
// Generates a new rewards tree based on the command line flags
@@ -168,18 +168,18 @@ func GenerateTree(c *cli.Context) error {
168168

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

185185
// initialize the generator targets
@@ -424,6 +424,14 @@ func (g *treeGenerator) generateRewardsFile(treegen *rprewards.TreeGenerator) (*
424424
return treegen.GenerateTreeWithRuleset(g.ruleset)
425425
}
426426

427+
func (g *treeGenerator) serializeVotingPower(votingPowerFile *VotingPowerFile) ([]byte, error) {
428+
if g.prettyPrint {
429+
return json.MarshalIndent(votingPowerFile, "", "\t")
430+
}
431+
432+
return json.Marshal(votingPowerFile)
433+
}
434+
427435
// Serializes the minipool performance file into JSON
428436
func (g *treeGenerator) serializeMinipoolPerformance(result *rprewards.GenerateTreeResult) ([]byte, error) {
429437
perfFile := result.MinipoolPerformanceFile
@@ -445,7 +453,7 @@ func (g *treeGenerator) serializeRewardsTree(rewardsFile rprewards.IRewardsFile)
445453
}
446454

447455
// Writes both the performance file and the rewards file to disk
448-
func (g *treeGenerator) writeFiles(result *rprewards.GenerateTreeResult) error {
456+
func (g *treeGenerator) writeFiles(result *rprewards.GenerateTreeResult, votingPowerFile *VotingPowerFile) error {
449457
g.log.Printlnf("Saving JSON files...")
450458
rewardsFile := result.RewardsFile
451459
index := rewardsFile.GetIndex()
@@ -489,6 +497,21 @@ func (g *treeGenerator) writeFiles(result *rprewards.GenerateTreeResult) error {
489497
}
490498

491499
g.log.Printlnf("Saved rewards snapshot file to %s", rewardsTreePath)
500+
501+
// Write the voting power file to disk
502+
if votingPowerFile != nil {
503+
votingPowerFileBytes, err := g.serializeVotingPower(votingPowerFile)
504+
if err != nil {
505+
return fmt.Errorf("error serializing voting power file into JSON: %w", err)
506+
}
507+
508+
votingFilePath := filepath.Join(g.outputDir, fmt.Sprintf("rp-voting-power-%s-%d.json", string(g.cfg.Smartnode.Network.Value.(cfgtypes.Network)), votingPowerFile.ConsensusSlot))
509+
err = os.WriteFile(votingFilePath, votingPowerFileBytes, 0644)
510+
if err != nil {
511+
return fmt.Errorf("error saving voting power file to %s: %w", votingFilePath, err)
512+
}
513+
g.log.Printlnf("Saved voting power file to %s", votingFilePath)
514+
}
492515
g.log.Printlnf("Successfully generated rewards snapshot for interval %d", index)
493516

494517
return nil
@@ -564,6 +587,7 @@ func (g *treeGenerator) approximateRethSpRewards() error {
564587

565588
// Generate a complete rewards tree
566589
func (g *treeGenerator) generateTree() error {
590+
var votingPowerFile *VotingPowerFile
567591
args, err := g.getTreegenArgs()
568592
if err != nil {
569593
return fmt.Errorf("error compiling treegen arguments: %w", err)
@@ -575,6 +599,12 @@ func (g *treeGenerator) generateTree() error {
575599
return err
576600
}
577601

602+
// If a voting power file was requested, generate it now.
603+
if g.generateVotingPower {
604+
votingPowerFile = g.GenerateVotingPower(args.state)
605+
votingPowerFile.Time = args.endTime
606+
}
607+
578608
// Generate the rewards file
579609
start := time.Now()
580610
result, err := g.generateRewardsFile(treegen)
@@ -598,7 +628,7 @@ func (g *treeGenerator) generateTree() error {
598628
}
599629
}
600630

601-
err = g.writeFiles(result)
631+
err = g.writeFiles(result, votingPowerFile)
602632
if err != nil {
603633
return err
604634
}

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)