[#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:
parent
4770cb8bf6
commit
e8bf18c0b4
5 changed files with 59 additions and 3 deletions
|
@ -23,7 +23,15 @@ func (np *Processor) processNewEpoch(ev netmapEvent.NewEpoch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
np.epochState.SetEpochCounter(epoch)
|
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",
|
np.log.Warn("can't reset epoch timer",
|
||||||
zap.String("error", err.Error()))
|
zap.String("error", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
type (
|
type (
|
||||||
// EpochTimerReseter is a callback interface for tickers component.
|
// EpochTimerReseter is a callback interface for tickers component.
|
||||||
EpochTimerReseter interface {
|
EpochTimerReseter interface {
|
||||||
ResetEpochTimer() error
|
ResetEpochTimer(uint32) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// EpochState is a callback interface for inner ring global state.
|
// EpochState is a callback interface for inner ring global state.
|
||||||
|
|
|
@ -160,7 +160,8 @@ func (s *Server) WriteReport(r *audit.Report) error {
|
||||||
// ResetEpochTimer resets block timer that produces events to update epoch
|
// ResetEpochTimer resets block timer that produces events to update epoch
|
||||||
// counter in netmap contract. Used to synchronize this even production
|
// counter in netmap contract. Used to synchronize this even production
|
||||||
// based on block with notification of last epoch.
|
// 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()
|
return s.epochTimer.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
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
|
// NeoFSAlphabetList returns keys that stored in NeoFS Alphabet role. Main chain
|
||||||
// stores alphabet node keys of inner ring there, however side chain stores both
|
// stores alphabet node keys of inner ring there, however side chain stores both
|
||||||
// alphabet and non alphabet node keys of inner ring.
|
// alphabet and non alphabet node keys of inner ring.
|
||||||
|
|
|
@ -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) {
|
func TestBlockTimer(t *testing.T) {
|
||||||
blockDur := uint32(10)
|
blockDur := uint32(10)
|
||||||
baseCallCounter := uint32(0)
|
baseCallCounter := uint32(0)
|
||||||
|
|
Loading…
Reference in a new issue