From aa886f67ce0b55e17b0069a4892a1490236810ca Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 12 Apr 2022 17:29:11 +0300 Subject: [PATCH] core: use dao-binded cache for native contracts All native cached values are binded to DAO, so that it's possible to properly handle historic calls. --- pkg/compiler/interop_test.go | 3 +- pkg/core/blockchain.go | 43 +++++-- pkg/core/blockchain_core_test.go | 3 +- pkg/core/interop_system_core_test.go | 9 +- pkg/core/interops_test.go | 5 +- pkg/core/native/designate.go | 64 ++++++---- pkg/core/native/management.go | 112 +++++++++------- pkg/core/native/management_test.go | 12 +- pkg/core/native/native_gas.go | 2 +- pkg/core/native/native_neo.go | 183 ++++++++++++++++----------- pkg/core/native/notary.go | 67 ++++++---- pkg/core/native/oracle.go | 39 ++++-- pkg/core/native/policy.go | 165 ++++++++++++++---------- pkg/core/storage/memcached_store.go | 40 ++++++ pkg/rpc/server/client_test.go | 8 +- pkg/rpc/server/server.go | 2 +- 16 files changed, 475 insertions(+), 282 deletions(-) diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 3243f4a1d..120e29709 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -201,7 +201,8 @@ func TestAppCall(t *testing.T) { } fc := fakechain.NewFakeChain() - ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t)) + ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), + interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t)) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 02d930896..e53047c1d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -594,7 +594,18 @@ func (bc *Blockchain) initializeNativeCache(d *dao.Simple) error { if err != nil { return fmt.Errorf("can't init cache for Management native contract: %w", err) } - bc.contracts.Designate.InitializeCache() + bc.contracts.Designate.InitializeCache(d) + 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) + } + } + err = bc.contracts.Policy.InitializeCache(d) + if err != nil { + return fmt.Errorf("can't init cache for Policy native contract: %w", err) + } return nil } @@ -1223,14 +1234,14 @@ func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error { return nil } - newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress()} - nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal() + newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress(bc.dao)} + nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao) script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals) if err != nil { return err } newList = append(newList, hash.Hash160(script)) - bc.updateExtensibleList(&newList, bc.contracts.NEO.GetNextBlockValidatorsInternal()) + bc.updateExtensibleList(&newList, bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao)) if len(stateVals) > 0 { h, err := bc.contracts.Designate.GetLastDesignatedHash(bc.dao, noderoles.StateValidator) @@ -1454,12 +1465,12 @@ func (bc *Blockchain) ForEachNEP11Transfer(acc util.Uint160, newestTimestamp uin // GetNEP17Contracts returns the list of deployed NEP-17 contracts. func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { - return bc.contracts.Management.GetNEP17Contracts() + return bc.contracts.Management.GetNEP17Contracts(bc.dao) } // GetNEP11Contracts returns the list of deployed NEP-11 contracts. func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 { - return bc.contracts.Management.GetNEP11Contracts() + return bc.contracts.Management.GetNEP11Contracts(bc.dao) } // GetTokenLastUpdated returns a set of contract ids with the corresponding last updated @@ -1826,7 +1837,7 @@ func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*tra curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1) if oldVC == nil || oldVC != curVC { m := smartcontract.GetDefaultHonestNodeCount(curVC) - verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal()) + verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao)) defaultWitness = transaction.Witness{ InvocationScript: make([]byte, 66*m), VerificationScript: verification, @@ -1942,7 +1953,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. if err != nil { return err } - if err := bc.verifyTxAttributes(t, isPartialTx); err != nil { + if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil { return err } err = pool.Add(t, feer, data...) @@ -1966,11 +1977,11 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool. return nil } -func (bc *Blockchain) verifyTxAttributes(tx *transaction.Transaction, isPartialTx bool) error { +func (bc *Blockchain) verifyTxAttributes(d *dao.Simple, tx *transaction.Transaction, isPartialTx bool) error { for i := range tx.Attributes { switch attrType := tx.Attributes[i].Type; attrType { case transaction.HighPriority: - h := bc.contracts.NEO.GetCommitteeAddress() + h := bc.contracts.NEO.GetCommitteeAddress(d) if !tx.HasSigner(h) { return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute) } @@ -2064,7 +2075,7 @@ func (bc *Blockchain) IsTxStillRelevant(t *transaction.Transaction, txpool *memp } else if txpool.HasConflicts(t, bc) { return false } - if err := bc.verifyTxAttributes(t, isPartialTx); err != nil { + if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil { return false } for i := range t.Scripts { @@ -2122,7 +2133,7 @@ func (bc *Blockchain) PoolTxWithData(t *transaction.Transaction, data interface{ // GetCommittee returns the sorted list of public keys of nodes in committee. func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) { - pubs := bc.contracts.NEO.GetCommitteeMembers() + pubs := bc.contracts.NEO.GetCommitteeMembers(bc.dao) sort.Sort(pubs) return pubs, nil } @@ -2134,7 +2145,7 @@ func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) { // GetNextBlockValidators returns next block validators. func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) { - return bc.contracts.NEO.GetNextBlockValidatorsInternal(), nil + return bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao), nil } // GetEnrollments returns all registered validators. @@ -2173,6 +2184,12 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact s := mpt.NewTrieStore(sr.Root, mode, storage.NewPrivateMemCachedStore(bc.dao.Store)) dTrie := dao.NewSimple(s, bc.config.StateRootInHeader, bc.config.P2PSigExtensions) dTrie.Version = bc.dao.Version + // Initialize native cache before passing DAO to interop context constructor, because + // the constructor will call BaseExecFee/StoragePrice policy methods on the passed DAO. + err = bc.initializeNativeCache(dTrie) + if err != nil { + return nil, fmt.Errorf("failed to initialize native cache backed by historic DAO: %w", err) + } systemInterop := bc.newInteropContext(t, dTrie, b, tx) vm := systemInterop.SpawnVM() vm.SetPriceGetter(systemInterop.GetPrice) diff --git a/pkg/core/blockchain_core_test.go b/pkg/core/blockchain_core_test.go index fbfe43213..a0ccae047 100644 --- a/pkg/core/blockchain_core_test.go +++ b/pkg/core/blockchain_core_test.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -329,7 +328,7 @@ func TestBlockchain_BaseExecFeeBaseStoragePrice_Compat(t *testing.T) { bc := newTestChain(t) check := func(t *testing.T) { - ic := bc.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), bc.config.StateRootInHeader, bc.config.P2PSigExtensions), bc.topBlock.Load().(*block.Block), nil) + ic := bc.newInteropContext(trigger.Application, bc.dao, bc.topBlock.Load().(*block.Block), nil) require.Equal(t, bc.GetBaseExecFee(), ic.BaseExecFee()) require.Equal(t, bc.GetStoragePrice(), ic.BaseStorageFee()) } diff --git a/pkg/core/interop_system_core_test.go b/pkg/core/interop_system_core_test.go index 5224c0e4c..2407b5e46 100644 --- a/pkg/core/interop_system_core_test.go +++ b/pkg/core/interop_system_core_test.go @@ -530,10 +530,10 @@ func TestStorageFind(t *testing.T) { // Helper functions to create VM, InteropContext, TX, Account, Contract. -func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { +func createVM(t testing.TB) (*vm.VM, *interop.Context, *Blockchain) { chain := newTestChain(t) context := chain.newInteropContext(trigger.Application, - dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions), nil, nil) + dao.NewSimple(chain.dao.Store, chain.config.StateRootInHeader, chain.config.P2PSigExtensions), nil, nil) v := context.SpawnVM() return v, context, chain } @@ -552,10 +552,7 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C }, } - chain := newTestChain(t) - d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions) - context := chain.newInteropContext(trigger.Application, d, nil, nil) - v := context.SpawnVM() + v, context, chain := createVM(t) return v, contractState, context, chain } diff --git a/pkg/core/interops_test.go b/pkg/core/interops_test.go index ef4dde7bf..fb31c0e25 100644 --- a/pkg/core/interops_test.go +++ b/pkg/core/interops_test.go @@ -5,9 +5,7 @@ import ( "runtime" "testing" - "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/storage" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" @@ -17,8 +15,7 @@ func testNonInterop(t *testing.T, value interface{}, f func(*interop.Context) er v := vm.New() v.Estack().PushVal(value) chain := newTestChain(t) - d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions) - context := chain.newInteropContext(trigger.Application, d, nil, nil) + context := chain.newInteropContext(trigger.Application, chain.dao, nil, nil) context.VM = v require.Error(t, f(context)) } diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 7a33e667a..785bd2f9f 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -31,12 +31,6 @@ type Designate struct { interop.ContractMD NEO *NEO - rolesChangedFlag atomic.Value - oracles atomic.Value - stateVals atomic.Value - neofsAlphabet atomic.Value - notaries atomic.Value - // p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant. p2pSigExtensionsEnabled bool @@ -53,6 +47,14 @@ type roleData struct { height uint32 } +type DesignationCache struct { + rolesChangedFlag atomic.Value + oracles atomic.Value + stateVals atomic.Value + neofsAlphabet atomic.Value + notaries atomic.Value +} + const ( designateContractID = -8 @@ -104,6 +106,9 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate { // Initialize initializes Oracle contract. func (s *Designate) Initialize(ic *interop.Context) error { + cache := &DesignationCache{} + cache.rolesChangedFlag.Store(true) + ic.DAO.Store.SetCache(s.ID, cache) return nil } @@ -114,26 +119,27 @@ func (s *Designate) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (s *Designate) PostPersist(ic *interop.Context) error { - if !s.rolesChanged() { + cache := ic.DAO.Store.GetCache(s.ID).(*DesignationCache) + if !rolesChanged(cache) { return nil } - if err := s.updateCachedRoleData(&s.oracles, ic.DAO, noderoles.Oracle); err != nil { + if err := s.updateCachedRoleData(&cache.oracles, ic.DAO, noderoles.Oracle); err != nil { return err } - if err := s.updateCachedRoleData(&s.stateVals, ic.DAO, noderoles.StateValidator); err != nil { + if err := s.updateCachedRoleData(&cache.stateVals, ic.DAO, noderoles.StateValidator); err != nil { return err } - if err := s.updateCachedRoleData(&s.neofsAlphabet, ic.DAO, noderoles.NeoFSAlphabet); err != nil { + if err := s.updateCachedRoleData(&cache.neofsAlphabet, ic.DAO, noderoles.NeoFSAlphabet); err != nil { return err } if s.p2pSigExtensionsEnabled { - if err := s.updateCachedRoleData(&s.notaries, ic.DAO, noderoles.P2PNotary); err != nil { + if err := s.updateCachedRoleData(&cache.notaries, ic.DAO, noderoles.P2PNotary); err != nil { return err } } - s.rolesChangedFlag.Store(false) + cache.rolesChangedFlag.Store(false) return nil } @@ -162,8 +168,8 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It return pubsToArray(pubs) } -func (s *Designate) rolesChanged() bool { - rc := s.rolesChangedFlag.Load() +func rolesChanged(cache *DesignationCache) bool { + rc := cache.rolesChangedFlag.Load() return rc == nil || rc.(bool) } @@ -208,17 +214,17 @@ func (s *Designate) updateCachedRoleData(v *atomic.Value, d *dao.Simple, r noder return nil } -func (s *Designate) getCachedRoleData(r noderoles.Role) *roleData { +func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData { var val interface{} switch r { case noderoles.Oracle: - val = s.oracles.Load() + val = cache.oracles.Load() case noderoles.StateValidator: - val = s.stateVals.Load() + val = cache.stateVals.Load() case noderoles.NeoFSAlphabet: - val = s.neofsAlphabet.Load() + val = cache.neofsAlphabet.Load() case noderoles.P2PNotary: - val = s.notaries.Load() + val = cache.notaries.Load() } if val != nil { return val.(*roleData) @@ -231,8 +237,9 @@ func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util if !s.isValidRole(r) { return util.Uint160{}, ErrInvalidRole } - if !s.rolesChanged() { - if val := s.getCachedRoleData(r); val != nil { + cache := d.Store.GetCache(s.ID).(*DesignationCache) + if !rolesChanged(cache) { + if val := getCachedRoleData(cache, r); val != nil { return val.addr, nil } } @@ -249,8 +256,9 @@ func (s *Designate) GetDesignatedByRole(d *dao.Simple, r noderoles.Role, index u if !s.isValidRole(r) { return nil, 0, ErrInvalidRole } - if !s.rolesChanged() { - if val := s.getCachedRoleData(r); val != nil && val.height <= index { + cache := d.Store.GetCache(s.ID).(*DesignationCache) + if !rolesChanged(cache) { + if val := getCachedRoleData(cache, r); val != nil && val.height <= index { return val.nodes.Copy(), val.height, nil } } @@ -310,7 +318,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs if !s.isValidRole(r) { return ErrInvalidRole } - h := s.NEO.GetCommitteeAddress() + h := s.NEO.GetCommitteeAddress(ic.DAO) if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok { return ErrInvalidWitness } @@ -327,7 +335,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs } sort.Sort(pubs) nl := NodeList(pubs) - s.rolesChangedFlag.Store(true) + ic.DAO.Store.GetCache(s.ID).(*DesignationCache).rolesChangedFlag.Store(true) err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl) if err != nil { return err @@ -357,6 +365,8 @@ func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) { } // InitializeCache invalidates native Designate cache. -func (s *Designate) InitializeCache() { - s.rolesChangedFlag.Store(true) +func (s *Designate) InitializeCache(d *dao.Simple) { + cache := &DesignationCache{} + cache.rolesChangedFlag.Store(true) + d.Store.SetCache(s.ID, cache) } diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index dc9257331..9fcd8a6f1 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -30,7 +30,9 @@ import ( type Management struct { interop.ContractMD NEO *NEO +} +type ManagementCache struct { mtx sync.RWMutex contracts map[util.Uint160]*state.Contract // nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist. @@ -66,9 +68,6 @@ func MakeContractKey(h util.Uint160) []byte { func newManagement() *Management { var m = &Management{ ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), - contracts: make(map[util.Uint160]*state.Contract), - nep11: make(map[util.Uint160]struct{}), - nep17: make(map[util.Uint160]struct{}), } defer m.UpdateHash() @@ -146,9 +145,10 @@ func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) sta // GetContract returns contract with given hash from given DAO. func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) { - m.mtx.RLock() - cs, ok := m.contracts[hash] - m.mtx.RUnlock() + cache := d.Store.GetCache(m.ID).(*ManagementCache) + cache.mtx.RLock() + cs, ok := cache.contracts[hash] + cache.mtx.RUnlock() if !ok { return nil, storage.ErrKeyNotFound } else if cs != nil { @@ -260,11 +260,12 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item) return contractToStack(newcontract) } -func (m *Management) markUpdated(h util.Uint160) { - m.mtx.Lock() +func (m *Management) markUpdated(d *dao.Simple, h util.Uint160) { + cache := d.Store.GetCache(m.ID).(*ManagementCache) + cache.mtx.Lock() // Just set it to nil, to refresh cache in `PostPersist`. - m.contracts[h] = nil - m.mtx.Unlock() + cache.contracts[h] = nil + cache.mtx.Unlock() } // Deploy creates contract's hash/ID and saves new contract into the given DAO. @@ -300,7 +301,7 @@ func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File, if err != nil { return nil, err } - m.markUpdated(newcontract.Hash) + m.markUpdated(d, newcontract.Hash) return newcontract, nil } @@ -340,7 +341,7 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract. // if NEF was provided, update the contract script if neff != nil { - m.markUpdated(hash) + m.markUpdated(d, hash) contract.NEF = *neff } // if manifest was provided, update the contract manifest @@ -352,7 +353,7 @@ func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, ma if err != nil { return nil, fmt.Errorf("invalid manifest: %w", err) } - m.markUpdated(hash) + m.markUpdated(d, hash) contract.Manifest = *manif } err = checkScriptAndMethods(contract.NEF.Script, contract.Manifest.ABI.Methods) @@ -393,7 +394,7 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error { d.DeleteStorageItem(contract.ID, k) return true }) - m.markUpdated(hash) + m.markUpdated(d, hash) return nil } @@ -444,18 +445,19 @@ func (m *Management) Metadata() *interop.ContractMD { // updateContractCache saves contract in the common and NEP-related caches. It's // an internal method that must be called with m.mtx lock taken. -func (m *Management) updateContractCache(cs *state.Contract) { - m.contracts[cs.Hash] = cs +func updateContractCache(cache *ManagementCache, cs *state.Contract) { + cache.contracts[cs.Hash] = cs if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) { - m.nep11[cs.Hash] = struct{}{} + cache.nep11[cs.Hash] = struct{}{} } if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { - m.nep17[cs.Hash] = struct{}{} + cache.nep17[cs.Hash] = struct{}{} } } // OnPersist implements Contract interface. func (m *Management) OnPersist(ic *interop.Context) error { + var cache *ManagementCache for _, native := range ic.Natives { md := native.Metadata() history := md.UpdateHistory @@ -466,16 +468,19 @@ func (m *Management) OnPersist(ic *interop.Context) error { cs := &state.Contract{ ContractBase: md.ContractBase, } + if err := native.Initialize(ic); err != nil { + return fmt.Errorf("initializing %s native contract: %w", md.Name, err) + } err := m.PutContractState(ic.DAO, cs) if err != nil { return err } - if err := native.Initialize(ic); err != nil { - return fmt.Errorf("initializing %s native contract: %w", md.Name, err) + if cache == nil { + cache = ic.DAO.Store.GetCache(m.ID).(*ManagementCache) } - m.mtx.Lock() - m.updateContractCache(cs) - m.mtx.Unlock() + cache.mtx.Lock() + updateContractCache(cache, cs) + cache.mtx.Unlock() } return nil @@ -485,8 +490,11 @@ func (m *Management) OnPersist(ic *interop.Context) error { // Cache initialisation should be done apart from Initialize because Initialize is // called only when deploying native contracts. func (m *Management) InitializeCache(d *dao.Simple) error { - m.mtx.Lock() - defer m.mtx.Unlock() + cache := &ManagementCache{ + contracts: make(map[util.Uint160]*state.Contract), + nep11: make(map[util.Uint160]struct{}), + nep17: make(map[util.Uint160]struct{}), + } var initErr error d.Seek(m.ID, storage.SeekRange{Prefix: []byte{prefixContract}}, func(_, v []byte) bool { @@ -495,56 +503,63 @@ func (m *Management) InitializeCache(d *dao.Simple) error { if initErr != nil { return false } - m.updateContractCache(cs) + updateContractCache(cache, cs) return true }) - return initErr + if initErr != nil { + return initErr + } + d.Store.SetCache(m.ID, cache) + return nil } // PostPersist implements Contract interface. func (m *Management) PostPersist(ic *interop.Context) error { - m.mtx.Lock() - for h, cs := range m.contracts { + cache := ic.DAO.Store.GetCache(m.ID).(*ManagementCache) + cache.mtx.Lock() + defer cache.mtx.Unlock() + for h, cs := range cache.contracts { if cs != nil { continue } - delete(m.nep11, h) - delete(m.nep17, h) + delete(cache.nep11, h) + delete(cache.nep17, h) newCs, err := m.getContractFromDAO(ic.DAO, h) if err != nil { // Contract was destroyed. - delete(m.contracts, h) + delete(cache.contracts, h) continue } - m.updateContractCache(newCs) + updateContractCache(cache, newCs) } - m.mtx.Unlock() return nil } // GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list // is updated every PostPersist, so until PostPersist is called, the result for the previous block // is returned. -func (m *Management) GetNEP11Contracts() []util.Uint160 { - m.mtx.RLock() - result := make([]util.Uint160, 0, len(m.nep11)) - for h := range m.nep11 { +func (m *Management) GetNEP11Contracts(d *dao.Simple) []util.Uint160 { + cache := d.Store.GetCache(m.ID).(*ManagementCache) + cache.mtx.RLock() + result := make([]util.Uint160, 0, len(cache.nep11)) + for h := range cache.nep11 { result = append(result, h) } - m.mtx.RUnlock() + cache.mtx.RUnlock() return result } // GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list // is updated every PostPersist, so until PostPersist is called, the result for the previous block // is returned. -func (m *Management) GetNEP17Contracts() []util.Uint160 { - m.mtx.RLock() - result := make([]util.Uint160, 0, len(m.nep17)) - for h := range m.nep17 { +func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 { + cache := d.Store.GetCache(m.ID).(*ManagementCache) + cache.mtx.RLock() + result := make([]util.Uint160, 0, len(cache.nep17)) + for h := range cache.nep17 { result = append(result, h) } - m.mtx.RUnlock() + cache.mtx.RUnlock() return result } @@ -552,6 +567,13 @@ func (m *Management) GetNEP17Contracts() []util.Uint160 { func (m *Management) Initialize(ic *interop.Context) error { setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee) setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1) + + cache := &ManagementCache{ + contracts: make(map[util.Uint160]*state.Contract), + nep11: make(map[util.Uint160]struct{}), + nep17: make(map[util.Uint160]struct{}), + } + ic.DAO.Store.SetCache(m.ID, cache) return nil } @@ -561,7 +583,7 @@ func (m *Management) PutContractState(d *dao.Simple, cs *state.Contract) error { if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil { return err } - m.markUpdated(cs.Hash) + m.markUpdated(d, cs.Hash) if cs.UpdateCounter != 0 { // Update. return nil } diff --git a/pkg/core/native/management_test.go b/pkg/core/native/management_test.go index 15b6cf0c1..696a97a81 100644 --- a/pkg/core/native/management_test.go +++ b/pkg/core/native/management_test.go @@ -89,8 +89,10 @@ func TestManagement_GetNEP17Contracts(t *testing.T) { d := dao.NewSimple(storage.NewMemoryStore(), false, false) err := mgmt.Initialize(&interop.Context{DAO: d}) require.NoError(t, err) + err = mgmt.InitializeCache(d) + require.NoError(t, err) - require.Empty(t, mgmt.GetNEP17Contracts()) + require.Empty(t, mgmt.GetNEP17Contracts(d)) // Deploy NEP-17 contract script := []byte{byte(opcode.RET)} @@ -108,11 +110,11 @@ func TestManagement_GetNEP17Contracts(t *testing.T) { require.NoError(t, err) // PostPersist is not yet called, thus no NEP-17 contracts are expected - require.Empty(t, mgmt.GetNEP17Contracts()) + require.Empty(t, mgmt.GetNEP17Contracts(d)) // Call PostPersist, check c1 contract hash is returned require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d})) - require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts()) + require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d)) // Update contract manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{ @@ -124,9 +126,9 @@ func TestManagement_GetNEP17Contracts(t *testing.T) { require.NoError(t, err) // No changes expected before PostPersist call. - require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts()) + require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d)) // Call PostPersist, check c2 contract hash is returned require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d})) - require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts()) + require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts(d)) } diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index d02b672e5..14e83694c 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -108,7 +108,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error { absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee) g.burn(ic, tx.Sender(), absAmount) } - validators := g.NEO.GetNextBlockValidatorsInternal() + validators := g.NEO.GetNextBlockValidatorsInternal(ic.DAO) primary := validators[ic.Block.PrimaryIndex].GetScriptHash() var netFee int64 for _, tx := range ic.Block.Transactions { diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 830682790..681e77867 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -35,6 +35,13 @@ type NEO struct { GAS *GAS Policy *Policy + // Configuration and standby keys are set in constructor and then + // only read from. + cfg config.ProtocolConfiguration + standbyKeys keys.PublicKeys +} + +type NeoCache struct { // gasPerBlock represents current value of generated gas per block. // It is append-only and doesn't need to be copied when used. gasPerBlock atomic.Value @@ -58,11 +65,6 @@ type NEO struct { // It is set in state-modifying methods only and read in `PostPersist` thus is not protected // by any mutex. gasPerVoteCache map[string]big.Int - - // Configuration and standby keys are set in constructor and then - // only read from. - cfg config.ProtocolConfiguration - standbyKeys keys.PublicKeys } const ( @@ -129,13 +131,6 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO { nep17.balFromBytes = n.balanceFromBytes n.nep17TokenNative = *nep17 - n.votesChanged.Store(true) - n.nextValidators.Store(keys.PublicKeys(nil)) - n.validators.Store(keys.PublicKeys(nil)) - n.committee.Store(keysWithVotes(nil)) - n.committeeHash.Store(util.Uint160{}) - n.registerPriceChanged.Store(true) - n.gasPerVoteCache = make(map[string]big.Int) err := n.initConfigCache(cfg) if err != nil { @@ -213,9 +208,22 @@ func (n *NEO) Initialize(ic *interop.Context) error { return errors.New("already initialized") } + cache := &NeoCache{ + gasPerVoteCache: make(map[string]big.Int), + } + cache.votesChanged.Store(true) + cache.nextValidators.Store(keys.PublicKeys(nil)) + cache.validators.Store(keys.PublicKeys(nil)) + cache.committee.Store(keysWithVotes(nil)) + cache.committeeHash.Store(util.Uint160{}) + cache.registerPriceChanged.Store(true) + + // We need cache to be present in DAO before the subsequent call to `mint`. + ic.DAO.Store.SetCache(n.ID, cache) + committee0 := n.standbyKeys[:n.cfg.GetCommitteeSize(ic.Block.Index)] cvs := toKeysWithVotes(committee0) - err := n.updateCache(cvs, ic.Chain) + err := n.updateCache(cache, cvs, ic.Chain) if err != nil { return err } @@ -233,13 +241,14 @@ func (n *NEO) Initialize(ic *interop.Context) error { n.putGASRecord(ic.DAO, index, value) gr := &gasRecord{{Index: index, GASPerBlock: *value}} - n.gasPerBlock.Store(*gr) - n.gasPerBlockChanged.Store(false) + cache.gasPerBlock.Store(*gr) + cache.gasPerBlockChanged.Store(false) ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{}) setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice) - n.registerPrice.Store(int64(DefaultRegisterPrice)) - n.registerPriceChanged.Store(false) + cache.registerPrice.Store(int64(DefaultRegisterPrice)) + cache.registerPriceChanged.Store(false) + return nil } @@ -247,18 +256,30 @@ func (n *NEO) Initialize(ic *interop.Context) error { // Cache initialisation should be done apart from Initialize because Initialize is // called only when deploying native contracts. func (n *NEO) InitializeCache(bc interop.Ledger, d *dao.Simple) error { + cache := &NeoCache{ + gasPerVoteCache: make(map[string]big.Int), + } + + cache.votesChanged.Store(true) + cache.nextValidators.Store(keys.PublicKeys(nil)) + cache.validators.Store(keys.PublicKeys(nil)) + cache.committee.Store(keysWithVotes(nil)) + cache.committeeHash.Store(util.Uint160{}) + cache.registerPriceChanged.Store(true) + var committee = keysWithVotes{} si := d.GetStorageItem(n.ID, prefixCommittee) if err := committee.DecodeBytes(si); err != nil { - return err + return fmt.Errorf("failed to decode committee: %w", err) } - if err := n.updateCache(committee, bc); err != nil { - return err + if err := n.updateCache(cache, committee, bc); err != nil { + return fmt.Errorf("failed to update cache: %w", err) } - n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d)) - n.gasPerBlockChanged.Store(false) + cache.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d)) + cache.gasPerBlockChanged.Store(false) + d.Store.SetCache(n.ID, cache) return nil } @@ -270,27 +291,28 @@ func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error { return err } -func (n *NEO) updateCache(cvs keysWithVotes, bc interop.Ledger) error { - n.committee.Store(cvs) +func (n *NEO) updateCache(cache *NeoCache, cvs keysWithVotes, bc interop.Ledger) error { + cache.committee.Store(cvs) - var committee = n.GetCommitteeMembers() + var committee = getCommitteeMembers(cache) script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy()) if err != nil { return err } - n.committeeHash.Store(hash.Hash160(script)) + cache.committeeHash.Store(hash.Hash160(script)) + // TODO: use block height from interop context for proper historical calls handling. nextVals := committee[:n.cfg.GetNumOfCNs(bc.BlockHeight()+1)].Copy() sort.Sort(nextVals) - n.nextValidators.Store(nextVals) + cache.nextValidators.Store(nextVals) return nil } -func (n *NEO) updateCommittee(ic *interop.Context) error { - votesChanged := n.votesChanged.Load().(bool) +func (n *NEO) updateCommittee(cache *NeoCache, ic *interop.Context) error { + votesChanged := cache.votesChanged.Load().(bool) if !votesChanged { // We need to put in storage anyway, as it affects dumps - committee := n.committee.Load().(keysWithVotes) + committee := cache.committee.Load().(keysWithVotes) ic.DAO.PutStorageItem(n.ID, prefixCommittee, committee.Bytes()) return nil } @@ -299,10 +321,10 @@ func (n *NEO) updateCommittee(ic *interop.Context) error { if err != nil { return err } - if err := n.updateCache(cvs, ic.Chain); err != nil { + if err := n.updateCache(cache, cvs, ic.Chain); err != nil { return err } - n.votesChanged.Store(false) + cache.votesChanged.Store(false) ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes()) return nil } @@ -310,13 +332,14 @@ func (n *NEO) updateCommittee(ic *interop.Context) error { // OnPersist implements Contract interface. func (n *NEO) OnPersist(ic *interop.Context) error { if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) { - oldKeys := n.nextValidators.Load().(keys.PublicKeys) - oldCom := n.committee.Load().(keysWithVotes) + cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache) + oldKeys := cache.nextValidators.Load().(keys.PublicKeys) + oldCom := cache.committee.Load().(keysWithVotes) if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) || n.cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) { - n.votesChanged.Store(true) + cache.votesChanged.Store(true) } - if err := n.updateCommittee(ic); err != nil { + if err := n.updateCommittee(cache, ic); err != nil { return err } } @@ -326,7 +349,8 @@ func (n *NEO) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (n *NEO) PostPersist(ic *interop.Context) error { gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index) - pubs := n.GetCommitteeMembers() + cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache) + pubs := getCommitteeMembers(cache) committeeSize := n.cfg.GetCommitteeSize(ic.Block.Index) index := int(ic.Block.Index) % committeeSize committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio) @@ -340,7 +364,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error { voterReward.Div(voterReward, big.NewInt(int64(committeeSize+validatorsCount))) voterReward.Div(voterReward, big100) - var cs = n.committee.Load().(keysWithVotes) + var cs = cache.committee.Load().(keysWithVotes) var key = make([]byte, 38) for i := range cs { if cs[i].Votes.Sign() > 0 { @@ -356,7 +380,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error { key = makeVoterKey([]byte(cs[i].Key), key) var r *big.Int - if g, ok := n.gasPerVoteCache[cs[i].Key]; ok { + if g, ok := cache.gasPerVoteCache[cs[i].Key]; ok { r = &g } else { reward := n.getGASPerVote(ic.DAO, key[:34], []uint32{ic.Block.Index + 1}) @@ -365,21 +389,21 @@ func (n *NEO) PostPersist(ic *interop.Context) error { tmp.Add(tmp, r) binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1) - n.gasPerVoteCache[cs[i].Key] = *tmp + cache.gasPerVoteCache[cs[i].Key] = *tmp ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp)) } } } - if n.gasPerBlockChanged.Load().(bool) { - n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(ic.DAO)) - n.gasPerBlockChanged.Store(false) + if cache.gasPerBlockChanged.Load().(bool) { + cache.gasPerBlock.Store(n.getSortedGASRecordFromDAO(ic.DAO)) + cache.gasPerBlockChanged.Store(false) } - if n.registerPriceChanged.Load().(bool) { + if cache.registerPriceChanged.Load().(bool) { p := getIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}) - n.registerPrice.Store(p) - n.registerPriceChanged.Store(false) + cache.registerPrice.Store(p) + cache.registerPriceChanged.Store(false) } return nil } @@ -502,11 +526,12 @@ func (n *NEO) getSortedGASRecordFromDAO(d *dao.Simple) gasRecord { // GetGASPerBlock returns gas generated for block with provided index. func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int { + cache := d.Store.GetCache(n.ID).(*NeoCache) var gr gasRecord - if n.gasPerBlockChanged.Load().(bool) { + if cache.gasPerBlockChanged.Load().(bool) { gr = n.getSortedGASRecordFromDAO(d) } else { - gr = n.gasPerBlock.Load().(gasRecord) + gr = cache.gasPerBlock.Load().(gasRecord) } for i := len(gr) - 1; i >= 0; i-- { if gr[i].Index <= index { @@ -518,12 +543,13 @@ func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int { } // GetCommitteeAddress returns address of the committee. -func (n *NEO) GetCommitteeAddress() util.Uint160 { - return n.committeeHash.Load().(util.Uint160) +func (n *NEO) GetCommitteeAddress(d *dao.Simple) util.Uint160 { + cache := d.Store.GetCache(n.ID).(*NeoCache) + return cache.committeeHash.Load().(util.Uint160) } func (n *NEO) checkCommittee(ic *interop.Context) bool { - ok, err := runtime.CheckHashedWitness(ic, n.GetCommitteeAddress()) + ok, err := runtime.CheckHashedWitness(ic, n.GetCommitteeAddress(ic.DAO)) if err != nil { panic(err) } @@ -547,7 +573,8 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) er if !n.checkCommittee(ic) { return errors.New("invalid committee signature") } - n.gasPerBlockChanged.Store(true) + cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache) + cache.gasPerBlockChanged.Store(true) n.putGASRecord(ic.DAO, index, gas) return nil } @@ -557,8 +584,9 @@ func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackite } func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 { - if !n.registerPriceChanged.Load().(bool) { - return n.registerPrice.Load().(int64) + cache := d.Store.GetCache(n.ID).(*NeoCache) + if !cache.registerPriceChanged.Load().(bool) { + return cache.registerPrice.Load().(int64) } return getIntWithKey(n.ID, d, []byte{prefixRegisterPrice}) } @@ -573,11 +601,12 @@ func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stack } setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, price.Int64()) - n.registerPriceChanged.Store(true) + cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache) + cache.registerPriceChanged.Store(true) return stackitem.Null{} } -func (n *NEO) dropCandidateIfZero(d *dao.Simple, pub *keys.PublicKey, c *candidate) (bool, error) { +func (n *NEO) dropCandidateIfZero(d *dao.Simple, cache *NeoCache, pub *keys.PublicKey, c *candidate) (bool, error) { if c.Registered || c.Votes.Sign() != 0 { return false, nil } @@ -588,7 +617,7 @@ func (n *NEO) dropCandidateIfZero(d *dao.Simple, pub *keys.PublicKey, c *candida d.DeleteStorageItem(n.ID, append(voterKey, k...)) // d.Seek cuts prefix, thus need to append it again. return true }) - delete(n.gasPerVoteCache, string(voterKey)) + delete(cache.gasPerVoteCache, string(voterKey)) return true, nil } @@ -643,8 +672,9 @@ func (n *NEO) CalculateNEOHolderReward(d *dao.Simple, value *big.Int, start, end return nil, errors.New("negative value") } var gr gasRecord - if !n.gasPerBlockChanged.Load().(bool) { - gr = n.gasPerBlock.Load().(gasRecord) + cache := d.Store.GetCache(n.ID).(*NeoCache) + if !cache.gasPerBlockChanged.Load().(bool) { + gr = cache.gasPerBlock.Load().(gasRecord) } else { gr = n.getSortedGASRecordFromDAO(d) } @@ -717,10 +747,11 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK if si == nil { return nil } - n.validators.Store(keys.PublicKeys(nil)) + cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache) + cache.validators.Store(keys.PublicKeys(nil)) c := new(candidate).FromBytes(si) c.Registered = false - ok, err := n.dropCandidateIfZero(ic.DAO, pub, c) + ok, err := n.dropCandidateIfZero(ic.DAO, cache, pub, c) if ok { return err } @@ -796,7 +827,8 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public // ModifyAccountVotes modifies votes of the specified account by value (can be negative). // typ specifies if this modify is occurring during transfer or vote (with old or new validator). func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error { - n.votesChanged.Store(true) + cache := d.Store.GetCache(n.ID).(*NeoCache) + cache.votesChanged.Store(true) if acc.VoteTo != nil { key := makeValidatorKey(acc.VoteTo) si := d.GetStorageItem(n.ID, key) @@ -806,12 +838,12 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *bi cd := new(candidate).FromBytes(si) cd.Votes.Add(&cd.Votes, value) if !isNewVote { - ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd) + ok, err := n.dropCandidateIfZero(d, cache, acc.VoteTo, cd) if ok { return err } } - n.validators.Store(keys.PublicKeys(nil)) + cache.validators.Store(keys.PublicKeys(nil)) return putConvertibleToDAO(n.ID, d, key, cd) } return nil @@ -906,7 +938,8 @@ func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stacki // ComputeNextBlockValidators returns an actual list of current validators. func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, error) { numOfCNs := n.cfg.GetNumOfCNs(bc.BlockHeight() + 1) - if vals := n.validators.Load().(keys.PublicKeys); vals != nil && numOfCNs == len(vals) { + cache := d.Store.GetCache(n.ID).(*NeoCache) + if vals := cache.validators.Load().(keys.PublicKeys); vals != nil && numOfCNs == len(vals) { return vals.Copy(), nil } result, _, err := n.computeCommitteeMembers(bc, d) @@ -915,12 +948,12 @@ func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys } result = result[:numOfCNs] sort.Sort(result) - n.validators.Store(result) + cache.validators.Store(result) return result, nil } func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - pubs := n.GetCommitteeMembers() + pubs := n.GetCommitteeMembers(ic.DAO) sort.Sort(pubs) return pubsToArray(pubs) } @@ -939,8 +972,13 @@ func (n *NEO) modifyVoterTurnout(d *dao.Simple, amount *big.Int) error { } // GetCommitteeMembers returns public keys of nodes in committee using cached value. -func (n *NEO) GetCommitteeMembers() keys.PublicKeys { - var cvs = n.committee.Load().(keysWithVotes) +func (n *NEO) GetCommitteeMembers(d *dao.Simple) keys.PublicKeys { + cache := d.Store.GetCache(n.ID).(*NeoCache) + return getCommitteeMembers(cache) +} + +func getCommitteeMembers(cache *NeoCache) keys.PublicKeys { + var cvs = cache.committee.Load().(keysWithVotes) var committee = make(keys.PublicKeys, len(cvs)) var err error for i := range committee { @@ -1009,13 +1047,14 @@ func (n *NEO) computeCommitteeMembers(bc interop.Ledger, d *dao.Simple) (keys.Pu } func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - result := n.GetNextBlockValidatorsInternal() + result := n.GetNextBlockValidatorsInternal(ic.DAO) return pubsToArray(result) } // GetNextBlockValidatorsInternal returns next block validators. -func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys { - return n.nextValidators.Load().(keys.PublicKeys).Copy() +func (n *NEO) GetNextBlockValidatorsInternal(d *dao.Simple) keys.PublicKeys { + cache := d.Store.GetCache(n.ID).(*NeoCache) + return cache.nextValidators.Load().(keys.PublicKeys).Copy() } // BalanceOf returns native NEO token balance for the acc. diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index 086a11da1..18abcc31b 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -32,7 +32,9 @@ type Notary struct { GAS *GAS NEO *NEO Desig *Designate +} +type NotaryCache struct { lock sync.RWMutex // isValid defies whether cached values were changed during the current // consensus iteration. If false, these values will be updated after @@ -125,9 +127,23 @@ func (n *Notary) Metadata() *interop.ContractMD { func (n *Notary) Initialize(ic *interop.Context) error { setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey) - n.isValid = true - n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta - n.notaryServiceFeePerKey = defaultNotaryServiceFeePerKey + + cache := &NotaryCache{ + isValid: true, + maxNotValidBeforeDelta: defaultMaxNotValidBeforeDelta, + notaryServiceFeePerKey: defaultNotaryServiceFeePerKey, + } + ic.DAO.Store.SetCache(n.ID, cache) + return nil +} + +func (n *Notary) InitializeCache(d *dao.Simple) error { + cache := &NotaryCache{isValid: true} + + cache.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, d, maxNotValidBeforeDeltaKey)) + cache.notaryServiceFeePerKey = getIntWithKey(n.ID, d, notaryServiceFeeKey) + + d.Store.SetCache(n.ID, cache) return nil } @@ -176,15 +192,16 @@ func (n *Notary) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (n *Notary) PostPersist(ic *interop.Context) error { - n.lock.Lock() - defer n.lock.Unlock() - if n.isValid { + cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache) + cache.lock.Lock() + defer cache.lock.Unlock() + if cache.isValid { return nil } - n.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey)) - n.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey) - n.isValid = true + cache.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey)) + cache.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey) + cache.isValid = true return nil } @@ -391,10 +408,11 @@ func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.It // GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method. func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 { - n.lock.RLock() - defer n.lock.RUnlock() - if n.isValid { - return n.maxNotValidBeforeDelta + cache := dao.Store.GetCache(n.ID).(*NotaryCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return cache.maxNotValidBeforeDelta } return uint32(getIntWithKey(n.ID, dao, maxNotValidBeforeDeltaKey)) } @@ -410,10 +428,11 @@ func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem if !n.NEO.checkCommittee(ic) { panic("invalid committee signature") } - n.lock.Lock() - defer n.lock.Unlock() + cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache) + cache.lock.Lock() + defer cache.lock.Unlock() setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value)) - n.isValid = false + cache.isValid = false return stackitem.Null{} } @@ -424,10 +443,11 @@ func (n *Notary) getNotaryServiceFeePerKey(ic *interop.Context, _ []stackitem.It // GetNotaryServiceFeePerKey is an internal representation of Notary getNotaryServiceFeePerKey method. func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 { - n.lock.RLock() - defer n.lock.RUnlock() - if n.isValid { - return n.notaryServiceFeePerKey + cache := dao.Store.GetCache(n.ID).(*NotaryCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return cache.notaryServiceFeePerKey } return getIntWithKey(n.ID, dao, notaryServiceFeeKey) } @@ -441,10 +461,11 @@ func (n *Notary) setNotaryServiceFeePerKey(ic *interop.Context, args []stackitem if !n.NEO.checkCommittee(ic) { panic("invalid committee signature") } - n.lock.Lock() - defer n.lock.Unlock() + cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache) + cache.lock.Lock() + defer cache.lock.Unlock() setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value)) - n.isValid = false + cache.isValid = false return stackitem.Null{} } diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 63b813c5b..664728ca9 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -40,15 +40,17 @@ type Oracle struct { Desig *Designate oracleScript []byte - requestPrice atomic.Value - requestPriceChanged atomic.Value - // Module is an oracle module capable of talking with the external world. Module atomic.Value // newRequests contains new requests created during current block. newRequests map[uint64]*state.OracleRequest } +type OracleCache struct { + requestPrice atomic.Value + requestPriceChanged atomic.Value +} + const ( oracleContractID = -9 maxURLLength = 256 @@ -121,8 +123,6 @@ func newOracle() *Oracle { md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States) o.AddMethod(md, desc) - o.requestPriceChanged.Store(true) - return o } @@ -143,9 +143,10 @@ func (o *Oracle) OnPersist(ic *interop.Context) error { // PostPersist represents `postPersist` method. func (o *Oracle) PostPersist(ic *interop.Context) error { p := o.getPriceInternal(ic.DAO) - if o.requestPriceChanged.Load().(bool) { - o.requestPrice.Store(p) - o.requestPriceChanged.Store(false) + cache := ic.DAO.Store.GetCache(o.ID).(*OracleCache) + if cache.requestPriceChanged.Load().(bool) { + cache.requestPrice.Store(p) + cache.requestPriceChanged.Store(false) } var nodes keys.PublicKeys @@ -220,11 +221,21 @@ func (o *Oracle) Metadata() *interop.ContractMD { func (o *Oracle) Initialize(ic *interop.Context) error { setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0) setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice) - o.requestPrice.Store(int64(DefaultOracleRequestPrice)) - o.requestPriceChanged.Store(false) + + cache := &OracleCache{} + cache.requestPrice.Store(int64(DefaultOracleRequestPrice)) + cache.requestPriceChanged.Store(false) + ic.DAO.Store.SetCache(o.ID, cache) return nil } +func (o *Oracle) InitializeCache(d *dao.Simple) { + cache := &OracleCache{} + cache.requestPrice.Store(getIntWithKey(o.ID, d, prefixRequestPrice)) + cache.requestPriceChanged.Store(false) + d.Store.SetCache(o.ID, cache) +} + func getResponse(tx *transaction.Transaction) *transaction.OracleResponse { for i := range tx.Attributes { if tx.Attributes[i].Type == transaction.OracleResponseT { @@ -439,8 +450,9 @@ func (o *Oracle) getPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Ite } func (o *Oracle) getPriceInternal(d *dao.Simple) int64 { - if !o.requestPriceChanged.Load().(bool) { - return o.requestPrice.Load().(int64) + cache := d.Store.GetCache(o.ID).(*OracleCache) + if !cache.requestPriceChanged.Load().(bool) { + return cache.requestPrice.Load().(int64) } return getIntWithKey(o.ID, d, prefixRequestPrice) } @@ -454,7 +466,8 @@ func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem. panic("invalid committee signature") } setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64()) - o.requestPriceChanged.Store(true) + cache := ic.DAO.Store.GetCache(o.ID).(*OracleCache) + cache.requestPriceChanged.Store(true) return stackitem.Null{} } diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index c097b8e30..9623ead77 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -52,7 +52,10 @@ var ( // Policy represents Policy native contract. type Policy struct { interop.ContractMD - NEO *NEO + NEO *NEO +} + +type PolicyCache struct { lock sync.RWMutex // isValid defies whether cached values were changed during the current // consensus iteration. If false, these values will be updated after @@ -128,16 +131,52 @@ func (p *Policy) Initialize(ic *interop.Context) error { setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, defaultExecFeeFactor) setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice) - p.isValid = true - p.execFeeFactor = defaultExecFeeFactor - p.feePerByte = defaultFeePerByte - p.maxVerificationGas = defaultMaxVerificationGas - p.storagePrice = DefaultStoragePrice - p.blockedAccounts = make([]util.Uint160, 0) + cache := &PolicyCache{} + cache.isValid = true + cache.execFeeFactor = defaultExecFeeFactor + cache.feePerByte = defaultFeePerByte + cache.maxVerificationGas = defaultMaxVerificationGas + cache.storagePrice = DefaultStoragePrice + cache.blockedAccounts = make([]util.Uint160, 0) + ic.DAO.Store.SetCache(p.ID, cache) return nil } +func (p *Policy) InitializeCache(d *dao.Simple) error { + cache := &PolicyCache{} + err := p.fillCacheFromDAO(cache, d) + if err != nil { + return err + } + d.Store.SetCache(p.ID, cache) + return nil +} + +func (p *Policy) fillCacheFromDAO(cache *PolicyCache, d *dao.Simple) error { + cache.execFeeFactor = uint32(getIntWithKey(p.ID, d, execFeeFactorKey)) + cache.feePerByte = getIntWithKey(p.ID, d, feePerByteKey) + cache.maxVerificationGas = defaultMaxVerificationGas + cache.storagePrice = uint32(getIntWithKey(p.ID, d, storagePriceKey)) + + cache.blockedAccounts = make([]util.Uint160, 0) + var fErr error + d.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool { + hash, err := util.Uint160DecodeBytesBE(k) + if err != nil { + fErr = fmt.Errorf("failed to decode blocked account hash: %w", err) + return false + } + cache.blockedAccounts = append(cache.blockedAccounts, hash) + return true + }) + if fErr != nil { + return fmt.Errorf("failed to initialize blocked accounts: %w", fErr) + } + cache.isValid = true + return nil +} + // OnPersist implements Contract interface. func (p *Policy) OnPersist(ic *interop.Context) error { return nil @@ -145,32 +184,14 @@ func (p *Policy) OnPersist(ic *interop.Context) error { // PostPersist implements Contract interface. func (p *Policy) PostPersist(ic *interop.Context) error { - p.lock.Lock() - defer p.lock.Unlock() - if p.isValid { + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() + if cache.isValid { return nil } - p.execFeeFactor = uint32(getIntWithKey(p.ID, ic.DAO, execFeeFactorKey)) - p.feePerByte = getIntWithKey(p.ID, ic.DAO, feePerByteKey) - p.maxVerificationGas = defaultMaxVerificationGas - p.storagePrice = uint32(getIntWithKey(p.ID, ic.DAO, storagePriceKey)) - - p.blockedAccounts = make([]util.Uint160, 0) - var fErr error - ic.DAO.Seek(p.ID, storage.SeekRange{Prefix: []byte{blockedAccountPrefix}}, func(k, _ []byte) bool { - hash, err := util.Uint160DecodeBytesBE(k) - if err != nil { - fErr = fmt.Errorf("failed to decode blocked account hash: %w", err) - return false - } - p.blockedAccounts = append(p.blockedAccounts, hash) - return true - }) - if fErr == nil { - p.isValid = true - } - return fErr + return p.fillCacheFromDAO(cache, ic.DAO) } // getFeePerByte is Policy contract method and returns required transaction's fee @@ -181,18 +202,22 @@ func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackite // GetFeePerByteInternal returns required transaction's fee per byte. func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 { - p.lock.RLock() - defer p.lock.RUnlock() - if p.isValid { - return p.feePerByte + cache := dao.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return cache.feePerByte } return getIntWithKey(p.ID, dao, feePerByteKey) } // GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion. -func (p *Policy) GetMaxVerificationGas(_ *dao.Simple) int64 { - if p.isValid { - return p.maxVerificationGas +func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 { + cache := dao.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return cache.maxVerificationGas } return defaultMaxVerificationGas } @@ -203,10 +228,11 @@ func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stack // GetExecFeeFactorInternal returns current execution fee factor. func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 { - p.lock.RLock() - defer p.lock.RUnlock() - if p.isValid { - return int64(p.execFeeFactor) + cache := d.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return int64(cache.execFeeFactor) } return getIntWithKey(p.ID, d, execFeeFactorKey) } @@ -219,10 +245,11 @@ func (p *Policy) setExecFeeFactor(ic *interop.Context, args []stackitem.Item) st if !p.NEO.checkCommittee(ic) { panic("invalid committee signature") } - p.lock.Lock() - defer p.lock.Unlock() + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value)) - p.isValid = false + cache.isValid = false return stackitem.Null{} } @@ -234,14 +261,15 @@ func (p *Policy) isBlocked(ic *interop.Context, args []stackitem.Item) stackitem // IsBlockedInternal checks whether provided account is blocked. func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool { - p.lock.RLock() - defer p.lock.RUnlock() - if p.isValid { - length := len(p.blockedAccounts) + cache := dao.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + length := len(cache.blockedAccounts) i := sort.Search(length, func(i int) bool { - return !p.blockedAccounts[i].Less(hash) + return !cache.blockedAccounts[i].Less(hash) }) - if length != 0 && i != length && p.blockedAccounts[i].Equals(hash) { + if length != 0 && i != length && cache.blockedAccounts[i].Equals(hash) { return true } return false @@ -256,10 +284,11 @@ func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stacki // GetStoragePriceInternal returns current execution fee factor. func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 { - p.lock.RLock() - defer p.lock.RUnlock() - if p.isValid { - return int64(p.storagePrice) + cache := d.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.RLock() + defer cache.lock.RUnlock() + if cache.isValid { + return int64(cache.storagePrice) } return getIntWithKey(p.ID, d, storagePriceKey) } @@ -272,10 +301,11 @@ func (p *Policy) setStoragePrice(ic *interop.Context, args []stackitem.Item) sta if !p.NEO.checkCommittee(ic) { panic("invalid committee signature") } - p.lock.Lock() - defer p.lock.Unlock() + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value)) - p.isValid = false + cache.isValid = false return stackitem.Null{} } @@ -288,10 +318,11 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack if !p.NEO.checkCommittee(ic) { panic("invalid committee signature") } - p.lock.Lock() - defer p.lock.Unlock() + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() setIntWithKey(p.ID, ic.DAO, feePerByteKey, value) - p.isValid = false + cache.isValid = false return stackitem.Null{} } @@ -311,10 +342,11 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki return stackitem.NewBool(false) } key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) - p.lock.Lock() - defer p.lock.Unlock() + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{}) - p.isValid = false + cache.isValid = false return stackitem.NewBool(true) } @@ -329,10 +361,11 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac return stackitem.NewBool(false) } key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) - p.lock.Lock() - defer p.lock.Unlock() + cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache) + cache.lock.Lock() + defer cache.lock.Unlock() ic.DAO.DeleteStorageItem(p.ID, key) - p.isValid = false + cache.isValid = false return stackitem.NewBool(true) } diff --git a/pkg/core/storage/memcached_store.go b/pkg/core/storage/memcached_store.go index 38623d9e4..e241a3b39 100644 --- a/pkg/core/storage/memcached_store.go +++ b/pkg/core/storage/memcached_store.go @@ -15,6 +15,9 @@ import ( type MemCachedStore struct { MemoryStore + nativeCacheLock sync.RWMutex + nativeCache map[int32]*NativeCacheItem + private bool // plock protects Persist from double entrance. plock sync.Mutex @@ -22,6 +25,10 @@ type MemCachedStore struct { ps Store } +type NativeCacheItem struct { + Value interface{} +} + type ( // KeyValue represents key-value pair. KeyValue struct { @@ -46,8 +53,15 @@ type ( // NewMemCachedStore creates a new MemCachedStore object. func NewMemCachedStore(lower Store) *MemCachedStore { + var cache map[int32]*NativeCacheItem + if cached, ok := lower.(*MemCachedStore); ok { + cache = cached.nativeCache + } else { + cache = make(map[int32]*NativeCacheItem) + } return &MemCachedStore{ MemoryStore: *NewMemoryStore(), + nativeCache: cache, ps: lower, } } @@ -55,8 +69,15 @@ func NewMemCachedStore(lower Store) *MemCachedStore { // NewPrivateMemCachedStore creates a new private (unlocked) MemCachedStore object. // Private cached stores are closed after Persist. func NewPrivateMemCachedStore(lower Store) *MemCachedStore { + var cache map[int32]*NativeCacheItem + if cached, ok := lower.(*MemCachedStore); ok { + cache = cached.nativeCache + } else { + cache = make(map[int32]*NativeCacheItem) + } return &MemCachedStore{ MemoryStore: *NewMemoryStore(), + nativeCache: cache, private: true, ps: lower, } @@ -393,6 +414,25 @@ func (s *MemCachedStore) persist(isSync bool) (int, error) { return keys, err } +func (s *MemCachedStore) GetCache(k int32) interface{} { + s.nativeCacheLock.RLock() + defer s.nativeCacheLock.RUnlock() + + if itm, ok := s.nativeCache[k]; ok { + return itm.Value + } + return nil +} + +func (s *MemCachedStore) SetCache(k int32, v interface{}) { + s.nativeCacheLock.Lock() + defer s.nativeCacheLock.Unlock() + + s.nativeCache[k] = &NativeCacheItem{ + Value: v, + } +} + // Close implements Store interface, clears up memory and closes the lower layer // Store. func (s *MemCachedStore) Close() error { diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index ca2ac2b63..14826ea13 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -4,9 +4,11 @@ import ( "context" "encoding/base64" "encoding/hex" + "strings" "testing" "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -798,13 +800,13 @@ func TestInvokeVerify(t *testing.T) { var h uint32 = 1 _, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) require.Error(t, err) - // TODO: check that error is `ErrUnknownVerificationContract` + require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet }) t.Run("bad, historic, by block: contract not found", func(t *testing.T) { _, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) require.Error(t, err) - // TODO: check that error is `ErrUnknownVerificationContract` + require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet }) t.Run("bad, historic, by stateroot: contract not found", func(t *testing.T) { @@ -813,7 +815,7 @@ func TestInvokeVerify(t *testing.T) { require.NoError(t, err) _, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) require.Error(t, err) - // TODO: check that error is `ErrUnknownVerificationContract` + require.True(t, strings.Contains(err.Error(), core.ErrUnknownVerificationContract.Error())) // contract wasn't deployed at block #1 yet }) t.Run("positive, with signer and witness", func(t *testing.T) { diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 590a44662..e370fb8f4 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -1840,7 +1840,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash err = s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}}) if err != nil { - return nil, response.NewInternalServerError("can't prepare verification VM", err) + return nil, response.NewInternalServerError(fmt.Sprintf("can't prepare verification VM: %s", err.Error()), err) } } else { ic.VM.LoadScriptWithFlags(script, callflag.All)