-
Notifications
You must be signed in to change notification settings - Fork 112
/
rewards-file-v1.go
240 lines (208 loc) · 8.62 KB
/
rewards-file-v1.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package rewards
import (
"encoding/hex"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/goccy/go-json"
"github.com/rocket-pool/rocketpool-go/types"
"github.com/rocket-pool/rocketpool-go/utils/eth"
"github.com/wealdtech/go-merkletree"
"github.com/wealdtech/go-merkletree/keccak256"
)
// Holds information
type MinipoolPerformanceFile_v1 struct {
Index uint64 `json:"index"`
Network string `json:"network"`
StartTime time.Time `json:"startTime,omitempty"`
EndTime time.Time `json:"endTime,omitempty"`
ConsensusStartBlock uint64 `json:"consensusStartBlock,omitempty"`
ConsensusEndBlock uint64 `json:"consensusEndBlock,omitempty"`
ExecutionStartBlock uint64 `json:"executionStartBlock,omitempty"`
ExecutionEndBlock uint64 `json:"executionEndBlock,omitempty"`
MinipoolPerformance map[common.Address]*SmoothingPoolMinipoolPerformance_v1 `json:"minipoolPerformance"`
}
// Serialize a minipool performance file into bytes
func (f *MinipoolPerformanceFile_v1) Serialize() ([]byte, error) {
return json.Marshal(f)
}
// Serialize a minipool performance file into bytes designed for human readability
func (f *MinipoolPerformanceFile_v1) SerializeHuman() ([]byte, error) {
return json.MarshalIndent(f, "", "\t")
}
// Deserialize a minipool performance file from bytes
func (f *MinipoolPerformanceFile_v1) Deserialize(bytes []byte) error {
return json.Unmarshal(bytes, &f)
}
// Get all of the minipool addresses with rewards in this file
// NOTE: the order of minipool addresses is not guaranteed to be stable, so don't rely on it
func (f *MinipoolPerformanceFile_v1) GetMinipoolAddresses() []common.Address {
addresses := make([]common.Address, len(f.MinipoolPerformance))
i := 0
for address := range f.MinipoolPerformance {
addresses[i] = address
i++
}
return addresses
}
// Get a minipool's smoothing pool performance if it was present
func (f *MinipoolPerformanceFile_v1) GetSmoothingPoolPerformance(minipoolAddress common.Address) (ISmoothingPoolMinipoolPerformance, bool) {
perf, exists := f.MinipoolPerformance[minipoolAddress]
return perf, exists
}
// Minipool stats
type SmoothingPoolMinipoolPerformance_v1 struct {
Pubkey string `json:"pubkey"`
StartSlot uint64 `json:"startSlot,omitempty"`
EndSlot uint64 `json:"endSlot,omitempty"`
ActiveFraction float64 `json:"activeFraction,omitempty"`
SuccessfulAttestations uint64 `json:"successfulAttestations"`
MissedAttestations uint64 `json:"missedAttestations"`
ParticipationRate float64 `json:"participationRate"`
MissingAttestationSlots []uint64 `json:"missingAttestationSlots"`
EthEarned float64 `json:"ethEarned"`
}
func (p *SmoothingPoolMinipoolPerformance_v1) GetPubkey() (types.ValidatorPubkey, error) {
return types.HexToValidatorPubkey(p.Pubkey)
}
func (p *SmoothingPoolMinipoolPerformance_v1) GetSuccessfulAttestationCount() uint64 {
return p.SuccessfulAttestations
}
func (p *SmoothingPoolMinipoolPerformance_v1) GetMissedAttestationCount() uint64 {
return p.MissedAttestations
}
func (p *SmoothingPoolMinipoolPerformance_v1) GetMissingAttestationSlots() []uint64 {
return p.MissingAttestationSlots
}
func (p *SmoothingPoolMinipoolPerformance_v1) GetEthEarned() *big.Int {
return eth.EthToWei(p.EthEarned)
}
// Node operator rewards
type NodeRewardsInfo_v1 struct {
RewardNetwork uint64 `json:"rewardNetwork"`
CollateralRpl *QuotedBigInt `json:"collateralRpl"`
OracleDaoRpl *QuotedBigInt `json:"oracleDaoRpl"`
SmoothingPoolEth *QuotedBigInt `json:"smoothingPoolEth"`
SmoothingPoolEligibilityRate float64 `json:"smoothingPoolEligibilityRate"`
MerkleData []byte `json:"-"`
MerkleProof []string `json:"merkleProof"`
}
func (i *NodeRewardsInfo_v1) GetRewardNetwork() uint64 {
return i.RewardNetwork
}
func (i *NodeRewardsInfo_v1) GetCollateralRpl() *QuotedBigInt {
return i.CollateralRpl
}
func (i *NodeRewardsInfo_v1) GetOracleDaoRpl() *QuotedBigInt {
return i.OracleDaoRpl
}
func (i *NodeRewardsInfo_v1) GetSmoothingPoolEth() *QuotedBigInt {
return i.SmoothingPoolEth
}
func (n *NodeRewardsInfo_v1) GetMerkleProof() ([]common.Hash, error) {
proof := []common.Hash{}
for _, proofLevel := range n.MerkleProof {
proof = append(proof, common.HexToHash(proofLevel))
}
return proof, nil
}
// JSON struct for a complete rewards file
type RewardsFile_v1 struct {
*RewardsFileHeader
NodeRewards map[common.Address]*NodeRewardsInfo_v1 `json:"nodeRewards"`
MinipoolPerformanceFile MinipoolPerformanceFile_v1 `json:"-"`
}
// Serialize a rewards file into bytes
func (f *RewardsFile_v1) Serialize() ([]byte, error) {
return json.Marshal(f)
}
// Deserialize a rewards file from bytes
func (f *RewardsFile_v1) Deserialize(bytes []byte) error {
return json.Unmarshal(bytes, &f)
}
// Get the rewards file's header
func (f *RewardsFile_v1) GetHeader() *RewardsFileHeader {
return f.RewardsFileHeader
}
// Get all of the node addresses with rewards in this file
// NOTE: the order of node addresses is not guaranteed to be stable, so don't rely on it
func (f *RewardsFile_v1) GetNodeAddresses() []common.Address {
addresses := make([]common.Address, len(f.NodeRewards))
i := 0
for address := range f.NodeRewards {
addresses[i] = address
i++
}
return addresses
}
// Get info about a node's rewards
func (f *RewardsFile_v1) GetNodeRewardsInfo(address common.Address) (INodeRewardsInfo, bool) {
rewards, exists := f.NodeRewards[address]
return rewards, exists
}
// Gets the minipool performance file corresponding to this rewards file
func (f *RewardsFile_v1) GetMinipoolPerformanceFile() IMinipoolPerformanceFile {
return &f.MinipoolPerformanceFile
}
// Sets the CID of the minipool performance file corresponding to this rewards file
func (f *RewardsFile_v1) SetMinipoolPerformanceFileCID(cid string) {
f.MinipoolPerformanceFileCID = cid
}
// Generates a merkle tree from the provided rewards map
func (f *RewardsFile_v1) generateMerkleTree() error {
// Generate the leaf data for each node
totalData := make([][]byte, 0, len(f.NodeRewards))
for address, rewardsForNode := range f.NodeRewards {
// Ignore nodes that didn't receive any rewards
if rewardsForNode.CollateralRpl.Cmp(common.Big0) == 0 && rewardsForNode.OracleDaoRpl.Cmp(common.Big0) == 0 && rewardsForNode.SmoothingPoolEth.Cmp(common.Big0) == 0 {
continue
}
// Node data is address[20] :: network[32] :: RPL[32] :: ETH[32]
nodeData := make([]byte, 0, 20+32*3)
// Node address
addressBytes := address.Bytes()
nodeData = append(nodeData, addressBytes...)
// Node network
network := big.NewInt(0).SetUint64(rewardsForNode.RewardNetwork)
networkBytes := make([]byte, 32)
network.FillBytes(networkBytes)
nodeData = append(nodeData, networkBytes...)
// RPL rewards
rplRewards := big.NewInt(0)
rplRewards.Add(&rewardsForNode.CollateralRpl.Int, &rewardsForNode.OracleDaoRpl.Int)
rplRewardsBytes := make([]byte, 32)
rplRewards.FillBytes(rplRewardsBytes)
nodeData = append(nodeData, rplRewardsBytes...)
// ETH rewards
ethRewardsBytes := make([]byte, 32)
rewardsForNode.SmoothingPoolEth.FillBytes(ethRewardsBytes)
nodeData = append(nodeData, ethRewardsBytes...)
// Assign it to the node rewards tracker and add it to the leaf data slice
rewardsForNode.MerkleData = nodeData
totalData = append(totalData, nodeData)
}
// Generate the tree
tree, err := merkletree.NewUsing(totalData, keccak256.New(), false, true)
if err != nil {
return fmt.Errorf("error generating Merkle Tree: %w", err)
}
// Generate the proofs for each node
for address, rewardsForNode := range f.NodeRewards {
// Get the proof
proof, err := tree.GenerateProof(rewardsForNode.MerkleData, 0)
if err != nil {
return fmt.Errorf("error generating proof for node %s: %w", address.Hex(), err)
}
// Convert the proof into hex strings
proofStrings := make([]string, len(proof.Hashes))
for i, hash := range proof.Hashes {
proofStrings[i] = fmt.Sprintf("0x%s", hex.EncodeToString(hash))
}
// Assign the hex strings to the node rewards struct
rewardsForNode.MerkleProof = proofStrings
}
f.MerkleTree = tree
f.MerkleRoot = common.BytesToHash(tree.Root()).Hex()
return nil
}