package innerring import ( "context" "fmt" "sort" "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/governance" control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sdnotify" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/state" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/spf13/viper" "go.uber.org/zap" ) const voteMethod = "vote" var ( persistateMainChainLastBlockKey = []byte("main_chain_last_processed_block") persistateSideChainLastBlockKey = []byte("side_chain_last_processed_block") ) // EpochCounter is a getter for a global epoch counter. func (s *Server) EpochCounter() uint64 { return s.epochCounter.Load() } // SetEpochCounter is a setter for contract processors to update global // epoch counter. func (s *Server) SetEpochCounter(val uint64) { s.epochCounter.Store(val) if s.irMetrics != nil { s.irMetrics.SetEpoch(val) } } // EpochDuration is a getter for a global epoch duration. func (s *Server) EpochDuration() uint64 { return s.epochDuration.Load() } // SetEpochDuration is a setter for the Netmap processor to update global // epoch duration. func (s *Server) SetEpochDuration(val uint64) { s.epochDuration.Store(val) } // IsActive is a getter for a global active flag state. func (s *Server) IsActive(ctx context.Context) bool { return s.InnerRingIndex(ctx) >= 0 } // IsAlphabet is a getter for a global alphabet flag state. func (s *Server) IsAlphabet(ctx context.Context) bool { return s.AlphabetIndex(ctx) >= 0 } // InnerRingIndex is a getter for a global index of node in inner ring list. Negative // index means that node is not in the inner ring list. func (s *Server) InnerRingIndex(ctx context.Context) int { index, err := s.statusIndex.InnerRingIndex() if err != nil { s.log.Error(ctx, logs.InnerringCantGetInnerRingIndex, zap.String("error", err.Error())) return -1 } return int(index) } // InnerRingSize is a getter for a global size of inner ring list. This value // paired with inner ring index. func (s *Server) InnerRingSize(ctx context.Context) int { size, err := s.statusIndex.InnerRingSize() if err != nil { s.log.Error(ctx, logs.InnerringCantGetInnerRingSize, zap.String("error", err.Error())) return 0 } return int(size) } // AlphabetIndex is a getter for a global index of node in alphabet list. // Negative index means that node is not in the alphabet list. func (s *Server) AlphabetIndex(ctx context.Context) int { index, err := s.statusIndex.AlphabetIndex() if err != nil { s.log.Error(ctx, logs.InnerringCantGetAlphabetIndex, zap.String("error", err.Error())) return -1 } return int(index) } func (s *Server) voteForSidechainValidator(ctx context.Context, prm governance.VoteValidatorPrm) error { validators := prm.Validators index := s.InnerRingIndex(ctx) if s.contracts.alphabet.indexOutOfRange(index) { s.log.Info(ctx, logs.InnerringIgnoreValidatorVoteNodeNotInAlphabetRange) return nil } if len(validators) == 0 { s.log.Info(ctx, logs.InnerringIgnoreValidatorVoteEmptyValidatorsList) return nil } epoch := s.EpochCounter() var ( nonce uint32 = 1 vub uint32 vubP *uint32 err error ) if prm.Hash != nil { nonce, vub, err = s.morphClient.CalculateNonceAndVUB(prm.Hash) if err != nil { return fmt.Errorf("could not calculate nonce and `validUntilBlock` values: %w", err) } vubP = &vub } s.contracts.alphabet.iterate(func(letter GlagoliticLetter, contract util.Uint160) { _, err := s.morphClient.NotaryInvoke(ctx, contract, s.feeConfig.SideChainFee(), nonce, vubP, voteMethod, epoch, validators) if err != nil { s.log.Warn(ctx, logs.InnerringCantInvokeVoteMethodInAlphabetContract, zap.Int8("alphabet_index", int8(letter)), zap.Uint64("epoch", epoch), zap.String("error", err.Error())) } }) return nil } // VoteForSidechainValidator calls vote method on alphabet contracts with // the provided list of keys. func (s *Server) VoteForSidechainValidator(ctx context.Context, prm governance.VoteValidatorPrm) error { sort.Sort(prm.Validators) return s.voteForSidechainValidator(ctx, prm) } // ResetEpochTimer resets the block timer that produces events to update epoch // counter in the netmap contract. It is used to synchronize this even production // based on the block with a notification of the last epoch. func (s *Server) ResetEpochTimer(h uint32) error { s.epochTimer.Tick(h) return s.epochTimer.Reset() } func (s *Server) setHealthStatus(ctx context.Context, hs control.HealthStatus) { s.healthStatus.Store(int32(hs)) s.notifySystemd(ctx, hs) if s.irMetrics != nil { s.irMetrics.SetHealth(int32(hs)) } } func (s *Server) CompareAndSwapHealthStatus(ctx context.Context, oldSt, newSt control.HealthStatus) (swapped bool) { if swapped = s.healthStatus.CompareAndSwap(int32(oldSt), int32(newSt)); swapped { s.notifySystemd(ctx, newSt) if s.irMetrics != nil { s.irMetrics.SetHealth(int32(newSt)) } } return } // HealthStatus returns the current health status of the IR application. func (s *Server) HealthStatus() control.HealthStatus { return control.HealthStatus(s.healthStatus.Load()) } func initPersistentStateStorage(cfg *viper.Viper) (*state.PersistentStorage, error) { persistPath := cfg.GetString("node.persistent_state.path") persistStorage, err := state.NewPersistentStorage(persistPath) if err != nil { return nil, fmt.Errorf("persistent state init error: %w", err) } return persistStorage, nil } func (s *Server) notifySystemd(ctx context.Context, st control.HealthStatus) { if !s.sdNotify { return } var err error switch st { case control.HealthStatus_READY: err = sdnotify.FlagAndStatus(sdnotify.ReadyEnabled) case control.HealthStatus_SHUTTING_DOWN: err = sdnotify.FlagAndStatus(sdnotify.StoppingEnabled) case control.HealthStatus_RECONFIGURING: err = sdnotify.FlagAndStatus(sdnotify.ReloadingEnabled) default: err = sdnotify.Status(fmt.Sprintf("%v", st)) } if err != nil { s.log.Error(ctx, logs.FailedToReportStatusToSystemd, zap.Error(err)) } }