diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index a70f7bb3a..064453ca5 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1012,9 +1012,14 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage) func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error { for _, c := range bc.contracts.Contracts { - err := c.InitializeCache(blockHeight, d) - if err != nil { - return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err) + for _, h := range c.Metadata().UpdateHistory { + if blockHeight >= h { // check that contract was deployed. + 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 diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 8a99ad04d..f5e2edcbf 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -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) { bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) { c.StateRootInHeader = true