Merge pull request #2994 from nspcc-dev/fix-init-cache
core: rework natives cache initialization
This commit is contained in:
commit
1bd22adcb3
14 changed files with 125 additions and 35 deletions
|
@ -1011,29 +1011,17 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
|
|||
}
|
||||
|
||||
func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
err := bc.contracts.NEO.InitializeCache(blockHeight, d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't init cache for NEO native contract: %w", err)
|
||||
}
|
||||
err = bc.contracts.Management.InitializeCache(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't init cache for Management native contract: %w", err)
|
||||
}
|
||||
err = bc.contracts.Designate.InitializeCache(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't init cache for Designation native contract: %w", err)
|
||||
}
|
||||
bc.contracts.Oracle.InitializeCache(d)
|
||||
if bc.P2PSigExtensionsEnabled() {
|
||||
err = bc.contracts.Notary.InitializeCache(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't init cache for Notary native contract: %w", err)
|
||||
for _, c := range bc.contracts.Contracts {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
err = bc.contracts.Policy.InitializeCache(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't init cache for Policy native contract: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2579,7 +2567,10 @@ func (bc *Blockchain) verifyTxAttributes(d *dao.Simple, tx *transaction.Transact
|
|||
nvb := tx.Attributes[i].Value.(*transaction.NotValidBefore).Height
|
||||
curHeight := bc.BlockHeight()
|
||||
if isPartialTx {
|
||||
maxNVBDelta := bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)
|
||||
maxNVBDelta, err := bc.GetMaxNotValidBeforeDelta()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: failed to retrieve MaxNotValidBeforeDelta value from native Notary contract: %v", ErrInvalidAttribute, err)
|
||||
}
|
||||
if curHeight+maxNVBDelta < nvb {
|
||||
return fmt.Errorf("%w: NotValidBefore (%d) bigger than MaxNVBDelta (%d) allows at height %d", ErrInvalidAttribute, nvb, maxNVBDelta, curHeight)
|
||||
}
|
||||
|
@ -2984,11 +2975,14 @@ func (bc *Blockchain) GetMaxVerificationGAS() int64 {
|
|||
}
|
||||
|
||||
// GetMaxNotValidBeforeDelta returns maximum NotValidBeforeDelta Notary limit.
|
||||
func (bc *Blockchain) GetMaxNotValidBeforeDelta() uint32 {
|
||||
func (bc *Blockchain) GetMaxNotValidBeforeDelta() (uint32, error) {
|
||||
if !bc.config.P2PSigExtensions {
|
||||
panic("disallowed call to Notary")
|
||||
panic("disallowed call to Notary") // critical error, thus panic.
|
||||
}
|
||||
return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)
|
||||
if bc.contracts.Notary.Metadata().UpdateHistory[0] > bc.BlockHeight() {
|
||||
return 0, fmt.Errorf("native Notary is active starting from %d", bc.contracts.Notary.Metadata().UpdateHistory[0])
|
||||
}
|
||||
return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao), nil
|
||||
}
|
||||
|
||||
// GetStoragePrice returns current storage price.
|
||||
|
|
|
@ -299,6 +299,69 @@ 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 return error.
|
||||
_, err = e.Chain.GetMaxNotValidBeforeDelta()
|
||||
require.Error(t, err)
|
||||
|
||||
// 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)
|
||||
}
|
||||
_, err = e.Chain.GetMaxNotValidBeforeDelta()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBlockchain_AddHeaders(t *testing.T) {
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) {
|
||||
c.StateRootInHeader = true
|
||||
|
@ -1884,11 +1947,15 @@ func TestBlockchain_VerifyTx(t *testing.T) {
|
|||
require.Error(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF))
|
||||
})
|
||||
t.Run("bad NVB: too big", func(t *testing.T) {
|
||||
tx := getPartiallyFilledTx(bc.BlockHeight()+bc.GetMaxNotValidBeforeDelta()+1, bc.BlockHeight()+1)
|
||||
maxNVB, err := bc.GetMaxNotValidBeforeDelta()
|
||||
require.NoError(t, err)
|
||||
tx := getPartiallyFilledTx(bc.BlockHeight()+maxNVB+1, bc.BlockHeight()+1)
|
||||
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), core.ErrInvalidAttribute))
|
||||
})
|
||||
t.Run("bad ValidUntilBlock: too small", func(t *testing.T) {
|
||||
tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+bc.GetMaxNotValidBeforeDelta()+1)
|
||||
maxNVB, err := bc.GetMaxNotValidBeforeDelta()
|
||||
require.NoError(t, err)
|
||||
tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+maxNVB+1)
|
||||
require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), core.ErrInvalidAttribute))
|
||||
})
|
||||
t.Run("good", func(t *testing.T) {
|
||||
|
|
|
@ -153,6 +153,11 @@ type MethodAndPrice struct {
|
|||
// Contract is an interface for all native contracts.
|
||||
type Contract interface {
|
||||
Initialize(*Context) error
|
||||
// InitializeCache aimed to initialize contract's cache when the contract has
|
||||
// been deployed, but in-memory cached data were lost due to the node reset.
|
||||
// It should be called each time after node restart iff the contract was
|
||||
// deployed and no Initialize method was called.
|
||||
InitializeCache(blockHeight uint32, d *dao.Simple) error
|
||||
Metadata() *ContractMD
|
||||
OnPersist(*Context) error
|
||||
PostPersist(*Context) error
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
|
@ -467,6 +468,11 @@ func (c *Crypto) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache implements the Contract interface.
|
||||
func (c *Crypto) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements the Contract interface.
|
||||
func (c *Crypto) OnPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
|
|
|
@ -132,7 +132,7 @@ func (s *Designate) Initialize(ic *interop.Context) error {
|
|||
|
||||
// InitializeCache fills native Designate cache from DAO. It is called at non-zero height, thus
|
||||
// we can fetch the roles data right from the storage.
|
||||
func (s *Designate) InitializeCache(d *dao.Simple) error {
|
||||
func (s *Designate) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &DesignationCache{}
|
||||
roles := []noderoles.Role{noderoles.Oracle, noderoles.NeoFSAlphabet, noderoles.StateValidator}
|
||||
if s.p2pSigExtensionsEnabled {
|
||||
|
|
|
@ -85,6 +85,11 @@ func (l *Ledger) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache implements the Contract interface.
|
||||
func (l *Ledger) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements the Contract interface.
|
||||
func (l *Ledger) OnPersist(ic *interop.Context) error {
|
||||
// Actual block/tx processing is done in Blockchain.storeBlock().
|
||||
|
|
|
@ -610,7 +610,7 @@ func (m *Management) OnPersist(ic *interop.Context) error {
|
|||
// InitializeCache initializes contract cache with the proper values from storage.
|
||||
// Cache initialization should be done apart from Initialize because Initialize is
|
||||
// called only when deploying native contracts.
|
||||
func (m *Management) InitializeCache(d *dao.Simple) error {
|
||||
func (m *Management) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &ManagementCache{
|
||||
contracts: make(map[util.Uint160]*state.Contract),
|
||||
nep11: make(map[util.Uint160]struct{}),
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestManagement_Initialize(t *testing.T) {
|
|||
t.Run("good", func(t *testing.T) {
|
||||
d := dao.NewSimple(storage.NewMemoryStore(), false, false)
|
||||
mgmt := newManagement()
|
||||
require.NoError(t, mgmt.InitializeCache(d))
|
||||
require.NoError(t, mgmt.InitializeCache(0, d))
|
||||
})
|
||||
/* See #2801
|
||||
t.Run("invalid contract state", func(t *testing.T) {
|
||||
|
@ -101,7 +101,7 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
|
|||
err := mgmt.Initialize(&interop.Context{DAO: d})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, mgmt.Policy.Initialize(&interop.Context{DAO: d}))
|
||||
err = mgmt.InitializeCache(d)
|
||||
err = mgmt.InitializeCache(0, d)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, mgmt.GetNEP17Contracts(d))
|
||||
|
|
|
@ -99,6 +99,11 @@ func (g *GAS) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache implements the Contract interface.
|
||||
func (g *GAS) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements the Contract interface.
|
||||
func (g *GAS) OnPersist(ic *interop.Context) error {
|
||||
if len(ic.Block.Transactions) == 0 {
|
||||
|
|
|
@ -305,7 +305,8 @@ func (n *NEO) Initialize(ic *interop.Context) error {
|
|||
|
||||
// InitializeCache initializes all NEO cache with the proper values from the storage.
|
||||
// Cache initialization should be done apart from Initialize because Initialize is
|
||||
// called only when deploying native contracts.
|
||||
// called only when deploying native contracts. InitializeCache implements the Contract
|
||||
// interface.
|
||||
func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &NeoCache{
|
||||
gasPerVoteCache: make(map[string]big.Int),
|
||||
|
|
|
@ -152,7 +152,7 @@ func (n *Notary) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (n *Notary) InitializeCache(d *dao.Simple) error {
|
||||
func (n *Notary) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &NotaryCache{
|
||||
maxNotValidBeforeDelta: uint32(getIntWithKey(n.ID, d, maxNotValidBeforeDeltaKey)),
|
||||
notaryServiceFeePerKey: getIntWithKey(n.ID, d, notaryServiceFeeKey),
|
||||
|
|
|
@ -251,10 +251,11 @@ func (o *Oracle) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *Oracle) InitializeCache(d *dao.Simple) {
|
||||
func (o *Oracle) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &OracleCache{}
|
||||
cache.requestPrice = getIntWithKey(o.ID, d, prefixRequestPrice)
|
||||
d.SetCache(o.ID, cache)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
|
||||
|
|
|
@ -153,7 +153,7 @@ func (p *Policy) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Policy) InitializeCache(d *dao.Simple) error {
|
||||
func (p *Policy) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
cache := &PolicyCache{}
|
||||
err := p.fillCacheFromDAO(cache, d)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
base58neogo "github.com/nspcc-dev/neo-go/pkg/encoding/base58"
|
||||
|
@ -429,6 +430,11 @@ func (s *Std) Initialize(ic *interop.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitializeCache implements the Contract interface.
|
||||
func (s *Std) InitializeCache(blockHeight uint32, d *dao.Simple) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPersist implements the Contract interface.
|
||||
func (s *Std) OnPersist(ic *interop.Context) error {
|
||||
return nil
|
||||
|
|
Loading…
Reference in a new issue