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  }