core: initialize natives cache wrt NativeActivations

If the contract was deployed then cache must be initialized after
in-memory data reset. If the contract isn't active yet, then no
cache will be initialized on deploy (i.e. on call to Initialize()
method by native Management).

Close #2984.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-04-26 13:22:13 +03:00
parent 33c971b0e4
commit edb2d46d5b
2 changed files with 73 additions and 3 deletions

View file

@ -1012,9 +1012,14 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error { func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error {
for _, c := range bc.contracts.Contracts { for _, c := range bc.contracts.Contracts {
err := c.InitializeCache(blockHeight, d) for _, h := range c.Metadata().UpdateHistory {
if err != nil { if blockHeight >= h { // check that contract was deployed.
return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err) err := c.InitializeCache(blockHeight, d)
if err != nil {
return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err)
}
break
}
} }
} }
return nil return nil

View file

@ -299,6 +299,71 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) {
}) })
} }
// This test enables Notary native contract at non-zero height and checks that no
// Notary cache initialization is performed before that height on node restart.
func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) {
const notaryEnabledHeight = 3
ps, path := newLevelDBForTestingWithPath(t, "")
customConfig := func(c *config.Blockchain) {
c.P2PSigExtensions = true
c.NativeUpdateHistories = make(map[string][]uint32)
for _, n := range []string{
nativenames.Neo,
nativenames.Gas,
nativenames.Designation,
nativenames.Management,
nativenames.CryptoLib,
nativenames.Ledger,
nativenames.Management,
nativenames.Oracle,
nativenames.Policy,
nativenames.StdLib,
nativenames.Notary,
} {
if n == nativenames.Notary {
c.NativeUpdateHistories[n] = []uint32{notaryEnabledHeight}
} else {
c.NativeUpdateHistories[n] = []uint32{0}
}
}
}
bc, validators, committee, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, ps)
require.NoError(t, err)
go bc.Run()
e := neotest.NewExecutor(t, bc, validators, committee)
e.AddNewBlock(t)
bc.Close() // Ensure persist is done and persistent store is properly closed.
ps, _ = newLevelDBForTestingWithPath(t, path)
// If NativeActivations are not taken into account during native cache initialization,
// bs.init() will panic on Notary cache initialization as it's not deployed yet.
require.NotPanics(t, func() {
bc, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, ps)
require.NoError(t, err)
})
go bc.Run()
defer bc.Close()
e = neotest.NewExecutor(t, bc, validators, committee)
h := e.Chain.BlockHeight()
// Notary isn't initialized yet, so accessing Notary cache should panic.
require.Panics(t, func() {
_ = e.Chain.GetMaxNotValidBeforeDelta()
})
// Ensure Notary will be properly initialized and accessing Notary cache works
// as expected.
for i := 0; i < notaryEnabledHeight; i++ {
require.NotPanics(t, func() {
e.AddNewBlock(t)
}, h+uint32(i)+1)
}
require.NotPanics(t, func() {
_ = e.Chain.GetMaxNotValidBeforeDelta()
})
}
func TestBlockchain_AddHeaders(t *testing.T) { func TestBlockchain_AddHeaders(t *testing.T) {
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) { bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) {
c.StateRootInHeader = true c.StateRootInHeader = true