github.com/ethersphere/bee/v2@v2.2.0/pkg/storageincentives/redistributionstate.go (about) 1 // Copyright 2023 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package storageincentives 6 7 import ( 8 "context" 9 "errors" 10 "math/big" 11 "sync" 12 "time" 13 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethersphere/bee/v2/pkg/log" 16 "github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20" 17 "github.com/ethersphere/bee/v2/pkg/storage" 18 storer "github.com/ethersphere/bee/v2/pkg/storer" 19 "github.com/ethersphere/bee/v2/pkg/swarm" 20 "github.com/ethersphere/bee/v2/pkg/transaction" 21 ) 22 23 const loggerNameNode = "nodestatus" 24 25 const ( 26 redistributionStatusKey = "redistribution_state" 27 purgeStaleDataThreshold = 10 28 ) 29 30 type RedistributionState struct { 31 mtx sync.Mutex 32 33 stateStore storage.StateStorer 34 erc20Service erc20.Service 35 logger log.Logger 36 ethAddress common.Address 37 status *Status 38 currentBalance *big.Int 39 txService transaction.Service 40 } 41 42 // Status provide internal status of the nodes in the redistribution game 43 type Status struct { 44 Phase PhaseType 45 IsFrozen bool 46 IsFullySynced bool 47 Round uint64 48 LastWonRound uint64 49 LastPlayedRound uint64 50 LastFrozenRound uint64 51 LastSelectedRound uint64 52 Block uint64 53 Reward *big.Int 54 Fees *big.Int 55 RoundData map[uint64]RoundData 56 SampleDuration time.Duration 57 IsHealthy bool 58 } 59 60 type RoundData struct { 61 CommitKey []byte 62 SampleData *SampleData 63 HasRevealed bool 64 } 65 66 type SampleData struct { 67 Anchor1 []byte 68 ReserveSampleItems []storer.SampleItem 69 ReserveSampleHash swarm.Address 70 StorageRadius uint8 71 } 72 73 func NewStatus() *Status { 74 return &Status{ 75 Reward: big.NewInt(0), 76 Fees: big.NewInt(0), 77 RoundData: make(map[uint64]RoundData), 78 } 79 } 80 81 func NewRedistributionState(logger log.Logger, ethAddress common.Address, stateStore storage.StateStorer, erc20Service erc20.Service, contract transaction.Service) (*RedistributionState, error) { 82 s := &RedistributionState{ 83 ethAddress: ethAddress, 84 stateStore: stateStore, 85 erc20Service: erc20Service, 86 logger: logger.WithName(loggerNameNode).Register(), 87 currentBalance: big.NewInt(0), 88 txService: contract, 89 status: NewStatus(), 90 } 91 92 status, err := s.Status() 93 if err != nil { 94 if !errors.Is(err, storage.ErrNotFound) { 95 return nil, err 96 } 97 status = NewStatus() 98 } 99 100 s.status = status 101 return s, nil 102 } 103 104 // Status returns the node status 105 func (r *RedistributionState) Status() (*Status, error) { 106 status := NewStatus() 107 if err := r.stateStore.Get(redistributionStatusKey, status); err != nil { 108 return nil, err 109 } 110 return status, nil 111 } 112 113 func (r *RedistributionState) save() { 114 err := r.stateStore.Put(redistributionStatusKey, r.status) 115 if err != nil { 116 r.logger.Error(err, "saving redistribution status") 117 } 118 } 119 120 func (r *RedistributionState) SetCurrentBlock(block uint64) { 121 r.mtx.Lock() 122 defer r.mtx.Unlock() 123 r.status.Block = block 124 r.save() 125 } 126 127 func (r *RedistributionState) SetCurrentEvent(phase PhaseType, round uint64) { 128 r.mtx.Lock() 129 defer r.mtx.Unlock() 130 r.status.Phase = phase 131 r.status.Round = round 132 r.save() 133 } 134 135 func (r *RedistributionState) IsFrozen() bool { 136 r.mtx.Lock() 137 defer r.mtx.Unlock() 138 139 return r.status.IsFrozen 140 } 141 142 func (r *RedistributionState) SetFrozen(isFrozen bool, round uint64) { 143 r.mtx.Lock() 144 defer r.mtx.Unlock() 145 if isFrozen && !r.status.IsFrozen { // record fronzen round if not set already 146 r.status.LastFrozenRound = round 147 } 148 r.status.IsFrozen = isFrozen 149 r.save() 150 } 151 152 func (r *RedistributionState) SetLastWonRound(round uint64) { 153 r.mtx.Lock() 154 defer r.mtx.Unlock() 155 r.status.LastWonRound = round 156 r.save() 157 } 158 159 func (r *RedistributionState) IsFullySynced() bool { 160 r.mtx.Lock() 161 defer r.mtx.Unlock() 162 163 return r.status.IsFullySynced 164 } 165 166 func (r *RedistributionState) SetFullySynced(isSynced bool) { 167 r.mtx.Lock() 168 defer r.mtx.Unlock() 169 r.status.IsFullySynced = isSynced 170 r.save() 171 } 172 173 func (r *RedistributionState) SetLastPlayedRound(round uint64) { 174 r.mtx.Lock() 175 defer r.mtx.Unlock() 176 r.status.LastPlayedRound = round 177 r.save() 178 } 179 180 func (r *RedistributionState) SetLastSelectedRound(round uint64) { 181 r.mtx.Lock() 182 defer r.mtx.Unlock() 183 r.status.LastSelectedRound = round 184 r.save() 185 } 186 187 // AddFee sets the internal node status 188 func (r *RedistributionState) AddFee(ctx context.Context, txHash common.Hash) { 189 fee, err := r.txService.TransactionFee(ctx, txHash) 190 if err != nil { 191 return 192 } 193 194 r.mtx.Lock() 195 defer r.mtx.Unlock() 196 197 r.status.Fees.Add(r.status.Fees, fee) 198 r.save() 199 } 200 201 // CalculateWinnerReward calculates the reward for the winner 202 func (r *RedistributionState) CalculateWinnerReward(ctx context.Context) error { 203 currentBalance, err := r.erc20Service.BalanceOf(ctx, r.ethAddress) 204 if err != nil { 205 r.logger.Debug("error getting balance", "error", err) 206 return err 207 } 208 209 r.mtx.Lock() 210 defer r.mtx.Unlock() 211 212 r.status.Reward.Add(r.status.Reward, currentBalance.Sub(currentBalance, r.currentBalance)) 213 r.save() 214 215 return nil 216 } 217 218 func (r *RedistributionState) SetBalance(ctx context.Context) error { 219 // get current balance 220 currentBalance, err := r.erc20Service.BalanceOf(ctx, r.ethAddress) 221 if err != nil { 222 r.logger.Debug("error getting balance", "error", err) 223 return err 224 } 225 226 r.mtx.Lock() 227 defer r.mtx.Unlock() 228 229 r.currentBalance.Set(currentBalance) 230 r.save() 231 232 return nil 233 } 234 235 func (r *RedistributionState) SampleData(round uint64) (SampleData, bool) { 236 r.mtx.Lock() 237 defer r.mtx.Unlock() 238 239 rd, ok := r.status.RoundData[round] 240 if !ok || rd.SampleData == nil { 241 return SampleData{}, false 242 } 243 244 return *rd.SampleData, true 245 } 246 247 func (r *RedistributionState) SetSampleData(round uint64, sd SampleData, dur time.Duration) { 248 r.mtx.Lock() 249 defer r.mtx.Unlock() 250 251 rd := r.status.RoundData[round] 252 rd.SampleData = &sd 253 r.status.RoundData[round] = rd 254 r.status.SampleDuration = dur 255 256 r.save() 257 } 258 259 func (r *RedistributionState) CommitKey(round uint64) ([]byte, bool) { 260 r.mtx.Lock() 261 defer r.mtx.Unlock() 262 263 rd, ok := r.status.RoundData[round] 264 if !ok || rd.CommitKey == nil { 265 return nil, false 266 } 267 268 return rd.CommitKey, true 269 } 270 271 func (r *RedistributionState) SetCommitKey(round uint64, commitKey []byte) { 272 r.mtx.Lock() 273 defer r.mtx.Unlock() 274 275 rd := r.status.RoundData[round] 276 rd.CommitKey = commitKey 277 r.status.RoundData[round] = rd 278 279 r.save() 280 } 281 282 func (r *RedistributionState) HasRevealed(round uint64) bool { 283 r.mtx.Lock() 284 defer r.mtx.Unlock() 285 286 rd := r.status.RoundData[round] 287 return rd.HasRevealed 288 } 289 290 func (r *RedistributionState) SetHealthy(isHealthy bool) { 291 r.mtx.Lock() 292 defer r.mtx.Unlock() 293 r.status.IsHealthy = isHealthy 294 r.save() 295 } 296 297 func (r *RedistributionState) IsHealthy() bool { 298 r.mtx.Lock() 299 defer r.mtx.Unlock() 300 return r.status.IsHealthy 301 } 302 303 func (r *RedistributionState) SetHasRevealed(round uint64) { 304 r.mtx.Lock() 305 defer r.mtx.Unlock() 306 307 rd := r.status.RoundData[round] 308 rd.HasRevealed = true 309 r.status.RoundData[round] = rd 310 311 r.save() 312 } 313 314 func (r *RedistributionState) currentRoundAndPhase() (uint64, PhaseType) { 315 r.mtx.Lock() 316 defer r.mtx.Unlock() 317 return r.status.Round, r.status.Phase 318 } 319 320 func (r *RedistributionState) currentBlock() uint64 { 321 r.mtx.Lock() 322 defer r.mtx.Unlock() 323 324 return r.status.Block 325 } 326 327 func (r *RedistributionState) purgeStaleRoundData() { 328 r.mtx.Lock() 329 defer r.mtx.Unlock() 330 331 currentRound := r.status.Round 332 333 if currentRound <= purgeStaleDataThreshold { 334 return 335 } 336 337 thresholdRound := currentRound - purgeStaleDataThreshold 338 hasChanged := false 339 340 for round := range r.status.RoundData { 341 if round < thresholdRound { 342 delete(r.status.RoundData, round) 343 hasChanged = true 344 } 345 } 346 347 if hasChanged { 348 r.save() 349 } 350 }