[#1208] innerring: Fix race condition between handling new epoch and block

Before resetting the timer, ensure the block tick is processed.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-04-04 10:07:33 +03:00 committed by Alex Vanin
parent 4770cb8bf6
commit e8bf18c0b4
5 changed files with 59 additions and 3 deletions

View file

@ -23,7 +23,15 @@ func (np *Processor) processNewEpoch(ev netmapEvent.NewEpoch) {
}
np.epochState.SetEpochCounter(epoch)
if err := np.epochTimer.ResetEpochTimer(); err != nil {
h, err := np.netmapClient.Morph().TxHeight(ev.TxHash())
if err != nil {
np.log.Warn("can't get transaction height",
zap.String("hash", ev.TxHash().StringLE()),
zap.String("error", err.Error()))
}
if err := np.epochTimer.ResetEpochTimer(h); err != nil {
np.log.Warn("can't reset epoch timer",
zap.String("error", err.Error()))
}

View file

@ -19,7 +19,7 @@ import (
type (
// EpochTimerReseter is a callback interface for tickers component.
EpochTimerReseter interface {
ResetEpochTimer() error
ResetEpochTimer(uint32) error
}
// EpochState is a callback interface for inner ring global state.

View file

@ -160,7 +160,8 @@ func (s *Server) WriteReport(r *audit.Report) error {
// ResetEpochTimer resets block timer that produces events to update epoch
// counter in netmap contract. Used to synchronize this even production
// based on block with notification of last epoch.
func (s *Server) ResetEpochTimer() error {
func (s *Server) ResetEpochTimer(h uint32) error {
s.epochTimer.Tick(h)
return s.epochTimer.Reset()
}

View file

@ -341,6 +341,18 @@ func (c *Client) TxHalt(h util.Uint256) (res bool, err error) {
return len(aer.Executions) > 0 && aer.Executions[0].VMState.HasFlag(vm.HaltState), nil
}
// TxHeight returns true if transaction has been successfully executed and persisted.
func (c *Client) TxHeight(h util.Uint256) (res uint32, err error) {
if c.multiClient != nil {
return res, c.multiClient.iterateClients(func(c *Client) error {
res, err = c.TxHeight(h)
return err
})
}
return c.client.GetTransactionHeight(h)
}
// NeoFSAlphabetList returns keys that stored in NeoFS Alphabet role. Main chain
// stores alphabet node keys of inner ring there, however side chain stores both
// alphabet and non alphabet node keys of inner ring.

View file

@ -13,6 +13,41 @@ func tickN(t *timer.BlockTimer, n uint32) {
}
}
// This test emulates inner-ring handling of a new epoch and a new block.
// "resetting" consists of ticking the current height as well and invoking `Reset`.
func TestIRBlockTimer_Reset(t *testing.T) {
var baseCounter [2]int
blockDur := uint32(3)
bt1 := timer.NewBlockTimer(
func() (uint32, error) { return blockDur, nil },
func() { baseCounter[0]++ })
bt2 := timer.NewBlockTimer(
func() (uint32, error) { return blockDur, nil },
func() { baseCounter[1]++ })
require.NoError(t, bt1.Reset())
require.NoError(t, bt2.Reset())
run := func(bt *timer.BlockTimer, direct bool) {
if direct {
bt.Tick(1)
require.NoError(t, bt.Reset())
bt.Tick(1)
} else {
bt.Tick(1)
bt.Tick(1)
require.NoError(t, bt.Reset())
}
bt.Tick(2)
bt.Tick(3)
}
run(bt1, true)
run(bt2, false)
require.Equal(t, baseCounter[0], baseCounter[1])
}
func TestBlockTimer(t *testing.T) {
blockDur := uint32(10)
baseCallCounter := uint32(0)