native: fix NeoToken initialization process
Refactored native NeoToken cache scheme introduced in #3110 sometimes requires validators list recalculation during native cache initialization process (when initializing with the existing storage from the block that is preceded each N-th block). To recalculate validators from candidates, native NeoToken needs an access to cached native Policy blocked accounts. By the moment of native Neo initialization, the cache of native Policy is not yet initialized, thus we need a direct DAO access for Policy to handle blocked account check. Close #3181. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
91c8aa21cc
commit
bbbc6805a8
2 changed files with 54 additions and 11 deletions
|
@ -301,6 +301,42 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// TestBlockchain_InitializeNeoCache_Bug3181 is aimed to reproduce and check situation
|
||||
// when panic occures on native Neo cache initialization due to access to native Policy
|
||||
// cache when it's not yet initialized to recalculate candidates.
|
||||
func TestBlockchain_InitializeNeoCache_Bug3181(t *testing.T) {
|
||||
ps, path := newLevelDBForTestingWithPath(t, "")
|
||||
bc, validators, committee, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, nil, ps)
|
||||
require.NoError(t, err)
|
||||
go bc.Run()
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
|
||||
// Add at least one registered candidate to enable candidates Policy check.
|
||||
acc := e.NewAccount(t, 10000_0000_0000) // block #1
|
||||
neo := e.NewInvoker(e.NativeHash(t, nativenames.Neo), acc)
|
||||
neo.Invoke(t, true, "registerCandidate", acc.(neotest.SingleSigner).Account().PublicKey().Bytes()) // block #2
|
||||
|
||||
// Put some empty blocks to reach N-1 block height, so that newEpoch cached
|
||||
// values of native Neo contract require an update on the subsequent cache
|
||||
// initialization.
|
||||
for i := 0; i < len(bc.GetConfig().StandbyCommittee)-1-2; i++ {
|
||||
e.AddNewBlock(t)
|
||||
}
|
||||
bc.Close() // Ensure persist is done and persistent store is properly closed.
|
||||
|
||||
ps, _ = newLevelDBForTestingWithPath(t, path)
|
||||
t.Cleanup(func() { require.NoError(t, ps.Close()) })
|
||||
|
||||
// Start chain from the existing database that should trigger an update of native
|
||||
// Neo newEpoch* cached values during initializaition. This update requires candidates
|
||||
// list recalculation and policies checks, thus, access to native Policy cache
|
||||
// that is not yet initialized by that moment.
|
||||
require.NotPanics(t, func() {
|
||||
_, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, nil, ps)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -241,26 +241,33 @@ func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) st
|
|||
// isBlocked is Policy contract method that checks whether provided account is blocked.
|
||||
func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
hash := toUint160(args[0])
|
||||
_, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||
_, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
|
||||
return stackitem.NewBool(blocked)
|
||||
}
|
||||
|
||||
// IsBlocked checks whether provided account is blocked.
|
||||
// IsBlocked checks whether provided account is blocked. Normally it uses Policy
|
||||
// cache, falling back to the DB queries when Policy cache is not available yet
|
||||
// (the only case is native cache initialization pipeline, where native Neo cache
|
||||
// is being initialized before the Policy's one).
|
||||
func (p *Policy) IsBlocked(dao *dao.Simple, hash util.Uint160) bool {
|
||||
_, isBlocked := p.isBlockedInternal(dao, hash)
|
||||
cache := dao.GetROCache(p.ID)
|
||||
if cache == nil {
|
||||
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
|
||||
return dao.GetStorageItem(p.ID, key) == nil
|
||||
}
|
||||
_, isBlocked := p.isBlockedInternal(cache.(*PolicyCache), hash)
|
||||
return isBlocked
|
||||
}
|
||||
|
||||
// isBlockedInternal checks whether provided account is blocked. It returns position
|
||||
// of the blocked account in the blocked accounts list (or the position it should be
|
||||
// put at).
|
||||
func (p *Policy) isBlockedInternal(dao *dao.Simple, hash util.Uint160) (int, bool) {
|
||||
cache := dao.GetROCache(p.ID).(*PolicyCache)
|
||||
length := len(cache.blockedAccounts)
|
||||
func (p *Policy) isBlockedInternal(roCache *PolicyCache, hash util.Uint160) (int, bool) {
|
||||
length := len(roCache.blockedAccounts)
|
||||
i := sort.Search(length, func(i int) bool {
|
||||
return !cache.blockedAccounts[i].Less(hash)
|
||||
return !roCache.blockedAccounts[i].Less(hash)
|
||||
})
|
||||
if length != 0 && i != length && cache.blockedAccounts[i].Equals(hash) {
|
||||
if length != 0 && i != length && roCache.blockedAccounts[i].Equals(hash) {
|
||||
return i, true
|
||||
}
|
||||
return i, false
|
||||
|
@ -320,7 +327,7 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
|
|||
return stackitem.NewBool(p.blockAccountInternal(ic.DAO, hash))
|
||||
}
|
||||
func (p *Policy) blockAccountInternal(d *dao.Simple, hash util.Uint160) bool {
|
||||
i, blocked := p.isBlockedInternal(d, hash)
|
||||
i, blocked := p.isBlockedInternal(d.GetROCache(p.ID).(*PolicyCache), hash)
|
||||
if blocked {
|
||||
return false
|
||||
}
|
||||
|
@ -343,7 +350,7 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
|||
panic("invalid committee signature")
|
||||
}
|
||||
hash := toUint160(args[0])
|
||||
i, blocked := p.isBlockedInternal(ic.DAO, hash)
|
||||
i, blocked := p.isBlockedInternal(ic.DAO.GetROCache(p.ID).(*PolicyCache), hash)
|
||||
if !blocked {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
|
@ -359,7 +366,7 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
|
|||
// fee limit.
|
||||
func (p *Policy) CheckPolicy(d *dao.Simple, tx *transaction.Transaction) error {
|
||||
for _, signer := range tx.Signers {
|
||||
if _, isBlocked := p.isBlockedInternal(d, signer.Account); isBlocked {
|
||||
if _, isBlocked := p.isBlockedInternal(d.GetROCache(p.ID).(*PolicyCache), signer.Account); isBlocked {
|
||||
return fmt.Errorf("account %s is blocked", signer.Account.StringLE())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue