From 62efa3f098177407faf1cc92ce7b078789cf3358 Mon Sep 17 00:00:00 2001
From: Alex Vanin <alexey@nspcc.ru>
Date: Wed, 21 Jul 2021 18:15:56 +0300
Subject: [PATCH] [#708] innerring: Synchronize initial epoch tick

When Inner Ring node starts, it should sync nearest epoch tick
event based on the block of the latest epoch. Otherwise epoch
ticking can be stopped, because ballots or notary transactions
are valid for limited period of time.

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
---
 pkg/innerring/innerring.go | 58 +++++++++++++++++++++++++++++++++-----
 1 file changed, 51 insertions(+), 7 deletions(-)

diff --git a/pkg/innerring/innerring.go b/pkg/innerring/innerring.go
index ec166b4f30..907038c9b5 100644
--- a/pkg/innerring/innerring.go
+++ b/pkg/innerring/innerring.go
@@ -25,6 +25,7 @@ import (
 	"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/reputation"
 	"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement"
 	auditSettlement "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/settlement/audit"
+	timerEvent "github.com/nspcc-dev/neofs-node/pkg/innerring/timers"
 	"github.com/nspcc-dev/neofs-node/pkg/morph/client"
 	auditWrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/audit/wrapper"
 	balanceWrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/balance/wrapper"
@@ -79,10 +80,14 @@ type (
 		sideNotaryConfig *notaryConfig
 
 		// internal variables
-		key                  *keys.PrivateKey
-		pubKey               []byte
-		contracts            *contracts
-		predefinedValidators keys.PublicKeys
+		key                   *keys.PrivateKey
+		pubKey                []byte
+		contracts             *contracts
+		predefinedValidators  keys.PublicKeys
+		initialEpochTickDelta uint32
+
+		// runtime processors
+		netmapProcessor *netmap.Processor
 
 		workers []func(context.Context)
 
@@ -201,6 +206,14 @@ func (s *Server) Start(ctx context.Context, intError chan<- error) (err error) {
 			zap.String("error", err.Error()))
 	}
 
+	// tick initial epoch
+	initialEpochTicker := timer.NewOneTickTimer(
+		timer.StaticBlockMeter(s.initialEpochTickDelta),
+		func() {
+			s.netmapProcessor.HandleNewEpochTick(timerEvent.NewEpochTick{})
+		})
+	s.addBlockTimer(initialEpochTicker)
+
 	morphErr := make(chan error)
 	mainnnetErr := make(chan error)
 
@@ -564,7 +577,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 	}
 
 	// create netmap processor
-	netmapProcessor, err := netmap.New(&netmap.Params{
+	server.netmapProcessor, err = netmap.New(&netmap.Params{
 		Log:              log,
 		PoolSize:         cfg.GetInt("workers.netmap"),
 		NetmapContract:   server.contracts.netmap,
@@ -591,7 +604,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 		return nil, err
 	}
 
-	err = bindMorphProcessor(netmapProcessor, server)
+	err = bindMorphProcessor(server.netmapProcessor, server)
 	if err != nil {
 		return nil, err
 	}
@@ -708,7 +721,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper) (*Server, error
 	// initialize epoch timers
 	server.epochTimer = newEpochTimer(&epochTimerArgs{
 		l:                  server.log,
-		nm:                 netmapProcessor,
+		nm:                 server.netmapProcessor,
 		cnrWrapper:         cnrClient,
 		epoch:              server,
 		epochDuration:      globalConfig.EpochDuration,
@@ -966,6 +979,12 @@ func (s *Server) initConfigFromBlockchain() error {
 		return fmt.Errorf("can't read balance contract precision: %w", err)
 	}
 
+	// get next epoch delta tick
+	s.initialEpochTickDelta, err = s.nextEpochBlockDelta()
+	if err != nil {
+		return err
+	}
+
 	s.epochCounter.Store(epoch)
 	s.precision.SetBalancePrecision(balancePrecision)
 
@@ -974,11 +993,36 @@ func (s *Server) initConfigFromBlockchain() error {
 		zap.Bool("alphabet", s.IsAlphabet()),
 		zap.Uint64("epoch", epoch),
 		zap.Uint32("precision", balancePrecision),
+		zap.Uint32("init_epoch_tick_delta", s.initialEpochTickDelta),
 	)
 
 	return nil
 }
 
+func (s *Server) nextEpochBlockDelta() (uint32, error) {
+	epochBlock, err := s.netmapClient.LastEpochBlock()
+	if err != nil {
+		return 0, fmt.Errorf("can't read last epoch block: %w", err)
+	}
+
+	blockHeight, err := s.morphClient.BlockCount()
+	if err != nil {
+		return 0, fmt.Errorf("can't get side chain height: %w", err)
+	}
+
+	epochDuration, err := s.netmapClient.EpochDuration()
+	if err != nil {
+		return 0, fmt.Errorf("can't get epoch duration: %w", err)
+	}
+
+	delta := uint32(epochDuration) + epochBlock
+	if delta < blockHeight {
+		return 0, nil
+	}
+
+	return delta - blockHeight, nil
+}
+
 // onlyActiveHandler wrapper around event handler that executes it
 // only if inner ring node state is active.
 func (s *Server) onlyActiveEventHandler(f event.Handler) event.Handler {