From e8bf18c0b448f6d30d2921be6af87c911f3c4571 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 4 Apr 2022 10:07:33 +0300 Subject: [PATCH] [#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 --- .../processors/netmap/process_epoch.go | 10 +++++- pkg/innerring/processors/netmap/processor.go | 2 +- pkg/innerring/state.go | 3 +- pkg/morph/client/client.go | 12 +++++++ pkg/morph/timer/block_test.go | 35 +++++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/pkg/innerring/processors/netmap/process_epoch.go b/pkg/innerring/processors/netmap/process_epoch.go index b2cefbcb92..cf7633839f 100644 --- a/pkg/innerring/processors/netmap/process_epoch.go +++ b/pkg/innerring/processors/netmap/process_epoch.go @@ -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())) } diff --git a/pkg/innerring/processors/netmap/processor.go b/pkg/innerring/processors/netmap/processor.go index 8734850f66..430a681993 100644 --- a/pkg/innerring/processors/netmap/processor.go +++ b/pkg/innerring/processors/netmap/processor.go @@ -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. diff --git a/pkg/innerring/state.go b/pkg/innerring/state.go index 1808cee64b..e079c68d1b 100644 --- a/pkg/innerring/state.go +++ b/pkg/innerring/state.go @@ -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() } diff --git a/pkg/morph/client/client.go b/pkg/morph/client/client.go index 14301e1755..ebc756f255 100644 --- a/pkg/morph/client/client.go +++ b/pkg/morph/client/client.go @@ -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. diff --git a/pkg/morph/timer/block_test.go b/pkg/morph/timer/block_test.go index 2d4afc4e07..59b484e6ec 100644 --- a/pkg/morph/timer/block_test.go +++ b/pkg/morph/timer/block_test.go @@ -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)