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.
This commit is contained in:
Anna Shaleva 2022-04-12 17:29:11 +03:00
parent 812fa3f76a
commit aa886f67ce
16 changed files with 475 additions and 282 deletions

View file

@ -201,7 +201,8 @@ func TestAppCall(t *testing.T) {
} }
fc := fakechain.NewFakeChain() 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) { t.Run("valid script", func(t *testing.T) {
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))

View file

@ -594,7 +594,18 @@ func (bc *Blockchain) initializeNativeCache(d *dao.Simple) error {
if err != nil { if err != nil {
return fmt.Errorf("can't init cache for Management native contract: %w", err) 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 return nil
} }
@ -1223,14 +1234,14 @@ func (bc *Blockchain) updateExtensibleWhitelist(height uint32) error {
return nil return nil
} }
newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress()} newList := []util.Uint160{bc.contracts.NEO.GetCommitteeAddress(bc.dao)}
nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal() nextVals := bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao)
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals) script, err := smartcontract.CreateDefaultMultiSigRedeemScript(nextVals)
if err != nil { if err != nil {
return err return err
} }
newList = append(newList, hash.Hash160(script)) 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 { if len(stateVals) > 0 {
h, err := bc.contracts.Designate.GetLastDesignatedHash(bc.dao, noderoles.StateValidator) 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. // GetNEP17Contracts returns the list of deployed NEP-17 contracts.
func (bc *Blockchain) GetNEP17Contracts() []util.Uint160 { 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. // GetNEP11Contracts returns the list of deployed NEP-11 contracts.
func (bc *Blockchain) GetNEP11Contracts() []util.Uint160 { 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 // 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) curVC := bc.config.GetNumOfCNs(bc.BlockHeight() + 1)
if oldVC == nil || oldVC != curVC { if oldVC == nil || oldVC != curVC {
m := smartcontract.GetDefaultHonestNodeCount(curVC) m := smartcontract.GetDefaultHonestNodeCount(curVC)
verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal()) verification, _ := smartcontract.CreateDefaultMultiSigRedeemScript(bc.contracts.NEO.GetNextBlockValidatorsInternal(bc.dao))
defaultWitness = transaction.Witness{ defaultWitness = transaction.Witness{
InvocationScript: make([]byte, 66*m), InvocationScript: make([]byte, 66*m),
VerificationScript: verification, VerificationScript: verification,
@ -1942,7 +1953,7 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
if err != nil { if err != nil {
return err return err
} }
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil { if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
return err return err
} }
err = pool.Add(t, feer, data...) err = pool.Add(t, feer, data...)
@ -1966,11 +1977,11 @@ func (bc *Blockchain) verifyAndPoolTx(t *transaction.Transaction, pool *mempool.
return nil 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 { for i := range tx.Attributes {
switch attrType := tx.Attributes[i].Type; attrType { switch attrType := tx.Attributes[i].Type; attrType {
case transaction.HighPriority: case transaction.HighPriority:
h := bc.contracts.NEO.GetCommitteeAddress() h := bc.contracts.NEO.GetCommitteeAddress(d)
if !tx.HasSigner(h) { if !tx.HasSigner(h) {
return fmt.Errorf("%w: high priority tx is not signed by committee", ErrInvalidAttribute) 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) { } else if txpool.HasConflicts(t, bc) {
return false return false
} }
if err := bc.verifyTxAttributes(t, isPartialTx); err != nil { if err := bc.verifyTxAttributes(bc.dao, t, isPartialTx); err != nil {
return false return false
} }
for i := range t.Scripts { 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. // GetCommittee returns the sorted list of public keys of nodes in committee.
func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) { func (bc *Blockchain) GetCommittee() (keys.PublicKeys, error) {
pubs := bc.contracts.NEO.GetCommitteeMembers() pubs := bc.contracts.NEO.GetCommitteeMembers(bc.dao)
sort.Sort(pubs) sort.Sort(pubs)
return pubs, nil return pubs, nil
} }
@ -2134,7 +2145,7 @@ func (bc *Blockchain) GetValidators() ([]*keys.PublicKey, error) {
// GetNextBlockValidators returns next block validators. // GetNextBlockValidators returns next block validators.
func (bc *Blockchain) GetNextBlockValidators() ([]*keys.PublicKey, error) { 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. // 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)) s := mpt.NewTrieStore(sr.Root, mode, storage.NewPrivateMemCachedStore(bc.dao.Store))
dTrie := dao.NewSimple(s, bc.config.StateRootInHeader, bc.config.P2PSigExtensions) dTrie := dao.NewSimple(s, bc.config.StateRootInHeader, bc.config.P2PSigExtensions)
dTrie.Version = bc.dao.Version 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) systemInterop := bc.newInteropContext(t, dTrie, b, tx)
vm := systemInterop.SpawnVM() vm := systemInterop.SpawnVM()
vm.SetPriceGetter(systemInterop.GetPrice) vm.SetPriceGetter(systemInterop.GetPrice)

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config" "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/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/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -329,7 +328,7 @@ func TestBlockchain_BaseExecFeeBaseStoragePrice_Compat(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
check := func(t *testing.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.GetBaseExecFee(), ic.BaseExecFee())
require.Equal(t, bc.GetStoragePrice(), ic.BaseStorageFee()) require.Equal(t, bc.GetStoragePrice(), ic.BaseStorageFee())
} }

View file

@ -530,10 +530,10 @@ func TestStorageFind(t *testing.T) {
// Helper functions to create VM, InteropContext, TX, Account, Contract. // 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) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, 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() v := context.SpawnVM()
return v, context, chain return v, context, chain
} }
@ -552,10 +552,7 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C
}, },
} }
chain := newTestChain(t) v, context, chain := createVM(t)
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
context := chain.newInteropContext(trigger.Application, d, nil, nil)
v := context.SpawnVM()
return v, contractState, context, chain return v, contractState, context, chain
} }

View file

@ -5,9 +5,7 @@ import (
"runtime" "runtime"
"testing" "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/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/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/stretchr/testify/require" "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 := vm.New()
v.Estack().PushVal(value) v.Estack().PushVal(value)
chain := newTestChain(t) chain := newTestChain(t)
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions) context := chain.newInteropContext(trigger.Application, chain.dao, nil, nil)
context := chain.newInteropContext(trigger.Application, d, nil, nil)
context.VM = v context.VM = v
require.Error(t, f(context)) require.Error(t, f(context))
} }

View file

@ -31,12 +31,6 @@ type Designate struct {
interop.ContractMD interop.ContractMD
NEO *NEO 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 defines whether the P2P signature extensions logic is relevant.
p2pSigExtensionsEnabled bool p2pSigExtensionsEnabled bool
@ -53,6 +47,14 @@ type roleData struct {
height uint32 height uint32
} }
type DesignationCache struct {
rolesChangedFlag atomic.Value
oracles atomic.Value
stateVals atomic.Value
neofsAlphabet atomic.Value
notaries atomic.Value
}
const ( const (
designateContractID = -8 designateContractID = -8
@ -104,6 +106,9 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
// Initialize initializes Oracle contract. // Initialize initializes Oracle contract.
func (s *Designate) Initialize(ic *interop.Context) error { func (s *Designate) Initialize(ic *interop.Context) error {
cache := &DesignationCache{}
cache.rolesChangedFlag.Store(true)
ic.DAO.Store.SetCache(s.ID, cache)
return nil return nil
} }
@ -114,26 +119,27 @@ func (s *Designate) OnPersist(ic *interop.Context) error {
// PostPersist implements Contract interface. // PostPersist implements Contract interface.
func (s *Designate) PostPersist(ic *interop.Context) error { func (s *Designate) PostPersist(ic *interop.Context) error {
if !s.rolesChanged() { cache := ic.DAO.Store.GetCache(s.ID).(*DesignationCache)
if !rolesChanged(cache) {
return nil 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 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 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 return err
} }
if s.p2pSigExtensionsEnabled { 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 return err
} }
} }
s.rolesChangedFlag.Store(false) cache.rolesChangedFlag.Store(false)
return nil return nil
} }
@ -162,8 +168,8 @@ func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.It
return pubsToArray(pubs) return pubsToArray(pubs)
} }
func (s *Designate) rolesChanged() bool { func rolesChanged(cache *DesignationCache) bool {
rc := s.rolesChangedFlag.Load() rc := cache.rolesChangedFlag.Load()
return rc == nil || rc.(bool) return rc == nil || rc.(bool)
} }
@ -208,17 +214,17 @@ func (s *Designate) updateCachedRoleData(v *atomic.Value, d *dao.Simple, r noder
return nil return nil
} }
func (s *Designate) getCachedRoleData(r noderoles.Role) *roleData { func getCachedRoleData(cache *DesignationCache, r noderoles.Role) *roleData {
var val interface{} var val interface{}
switch r { switch r {
case noderoles.Oracle: case noderoles.Oracle:
val = s.oracles.Load() val = cache.oracles.Load()
case noderoles.StateValidator: case noderoles.StateValidator:
val = s.stateVals.Load() val = cache.stateVals.Load()
case noderoles.NeoFSAlphabet: case noderoles.NeoFSAlphabet:
val = s.neofsAlphabet.Load() val = cache.neofsAlphabet.Load()
case noderoles.P2PNotary: case noderoles.P2PNotary:
val = s.notaries.Load() val = cache.notaries.Load()
} }
if val != nil { if val != nil {
return val.(*roleData) return val.(*roleData)
@ -231,8 +237,9 @@ func (s *Designate) GetLastDesignatedHash(d *dao.Simple, r noderoles.Role) (util
if !s.isValidRole(r) { if !s.isValidRole(r) {
return util.Uint160{}, ErrInvalidRole return util.Uint160{}, ErrInvalidRole
} }
if !s.rolesChanged() { cache := d.Store.GetCache(s.ID).(*DesignationCache)
if val := s.getCachedRoleData(r); val != nil { if !rolesChanged(cache) {
if val := getCachedRoleData(cache, r); val != nil {
return val.addr, 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) { if !s.isValidRole(r) {
return nil, 0, ErrInvalidRole return nil, 0, ErrInvalidRole
} }
if !s.rolesChanged() { cache := d.Store.GetCache(s.ID).(*DesignationCache)
if val := s.getCachedRoleData(r); val != nil && val.height <= index { if !rolesChanged(cache) {
if val := getCachedRoleData(cache, r); val != nil && val.height <= index {
return val.nodes.Copy(), val.height, nil 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) { if !s.isValidRole(r) {
return ErrInvalidRole return ErrInvalidRole
} }
h := s.NEO.GetCommitteeAddress() h := s.NEO.GetCommitteeAddress(ic.DAO)
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok { if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
return ErrInvalidWitness return ErrInvalidWitness
} }
@ -327,7 +335,7 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
} }
sort.Sort(pubs) sort.Sort(pubs)
nl := NodeList(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) err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
if err != nil { if err != nil {
return err return err
@ -357,6 +365,8 @@ func (s *Designate) getRole(item stackitem.Item) (noderoles.Role, bool) {
} }
// InitializeCache invalidates native Designate cache. // InitializeCache invalidates native Designate cache.
func (s *Designate) InitializeCache() { func (s *Designate) InitializeCache(d *dao.Simple) {
s.rolesChangedFlag.Store(true) cache := &DesignationCache{}
cache.rolesChangedFlag.Store(true)
d.Store.SetCache(s.ID, cache)
} }

View file

@ -30,7 +30,9 @@ import (
type Management struct { type Management struct {
interop.ContractMD interop.ContractMD
NEO *NEO NEO *NEO
}
type ManagementCache struct {
mtx sync.RWMutex mtx sync.RWMutex
contracts map[util.Uint160]*state.Contract contracts map[util.Uint160]*state.Contract
// nep11 is a map of NEP11-compliant contracts which is updated with every PostPersist. // 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 { func newManagement() *Management {
var m = &Management{ var m = &Management{
ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), 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() 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. // GetContract returns contract with given hash from given DAO.
func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) { func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
m.mtx.RLock() cache := d.Store.GetCache(m.ID).(*ManagementCache)
cs, ok := m.contracts[hash] cache.mtx.RLock()
m.mtx.RUnlock() cs, ok := cache.contracts[hash]
cache.mtx.RUnlock()
if !ok { if !ok {
return nil, storage.ErrKeyNotFound return nil, storage.ErrKeyNotFound
} else if cs != nil { } else if cs != nil {
@ -260,11 +260,12 @@ func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item)
return contractToStack(newcontract) return contractToStack(newcontract)
} }
func (m *Management) markUpdated(h util.Uint160) { func (m *Management) markUpdated(d *dao.Simple, h util.Uint160) {
m.mtx.Lock() cache := d.Store.GetCache(m.ID).(*ManagementCache)
cache.mtx.Lock()
// Just set it to nil, to refresh cache in `PostPersist`. // Just set it to nil, to refresh cache in `PostPersist`.
m.contracts[h] = nil cache.contracts[h] = nil
m.mtx.Unlock() cache.mtx.Unlock()
} }
// Deploy creates contract's hash/ID and saves new contract into the given DAO. // 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 { if err != nil {
return nil, err return nil, err
} }
m.markUpdated(newcontract.Hash) m.markUpdated(d, newcontract.Hash)
return newcontract, nil 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. contract = *oldcontract // Make a copy, don't ruin (potentially) cached contract.
// if NEF was provided, update the contract script // if NEF was provided, update the contract script
if neff != nil { if neff != nil {
m.markUpdated(hash) m.markUpdated(d, hash)
contract.NEF = *neff contract.NEF = *neff
} }
// if manifest was provided, update the contract manifest // 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 { if err != nil {
return nil, fmt.Errorf("invalid manifest: %w", err) return nil, fmt.Errorf("invalid manifest: %w", err)
} }
m.markUpdated(hash) m.markUpdated(d, hash)
contract.Manifest = *manif contract.Manifest = *manif
} }
err = checkScriptAndMethods(contract.NEF.Script, contract.Manifest.ABI.Methods) 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) d.DeleteStorageItem(contract.ID, k)
return true return true
}) })
m.markUpdated(hash) m.markUpdated(d, hash)
return nil return nil
} }
@ -444,18 +445,19 @@ func (m *Management) Metadata() *interop.ContractMD {
// updateContractCache saves contract in the common and NEP-related caches. It's // updateContractCache saves contract in the common and NEP-related caches. It's
// an internal method that must be called with m.mtx lock taken. // an internal method that must be called with m.mtx lock taken.
func (m *Management) updateContractCache(cs *state.Contract) { func updateContractCache(cache *ManagementCache, cs *state.Contract) {
m.contracts[cs.Hash] = cs cache.contracts[cs.Hash] = cs
if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) { if cs.Manifest.IsStandardSupported(manifest.NEP11StandardName) {
m.nep11[cs.Hash] = struct{}{} cache.nep11[cs.Hash] = struct{}{}
} }
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) { if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[cs.Hash] = struct{}{} cache.nep17[cs.Hash] = struct{}{}
} }
} }
// OnPersist implements Contract interface. // OnPersist implements Contract interface.
func (m *Management) OnPersist(ic *interop.Context) error { func (m *Management) OnPersist(ic *interop.Context) error {
var cache *ManagementCache
for _, native := range ic.Natives { for _, native := range ic.Natives {
md := native.Metadata() md := native.Metadata()
history := md.UpdateHistory history := md.UpdateHistory
@ -466,16 +468,19 @@ func (m *Management) OnPersist(ic *interop.Context) error {
cs := &state.Contract{ cs := &state.Contract{
ContractBase: md.ContractBase, 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) err := m.PutContractState(ic.DAO, cs)
if err != nil { if err != nil {
return err return err
} }
if err := native.Initialize(ic); err != nil { if cache == nil {
return fmt.Errorf("initializing %s native contract: %w", md.Name, err) cache = ic.DAO.Store.GetCache(m.ID).(*ManagementCache)
} }
m.mtx.Lock() cache.mtx.Lock()
m.updateContractCache(cs) updateContractCache(cache, cs)
m.mtx.Unlock() cache.mtx.Unlock()
} }
return nil 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 // Cache initialisation should be done apart from Initialize because Initialize is
// called only when deploying native contracts. // called only when deploying native contracts.
func (m *Management) InitializeCache(d *dao.Simple) error { func (m *Management) InitializeCache(d *dao.Simple) error {
m.mtx.Lock() cache := &ManagementCache{
defer m.mtx.Unlock() contracts: make(map[util.Uint160]*state.Contract),
nep11: make(map[util.Uint160]struct{}),
nep17: make(map[util.Uint160]struct{}),
}
var initErr error var initErr error
d.Seek(m.ID, storage.SeekRange{Prefix: []byte{prefixContract}}, func(_, v []byte) bool { 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 { if initErr != nil {
return false return false
} }
m.updateContractCache(cs) updateContractCache(cache, cs)
return true return true
}) })
if initErr != nil {
return initErr return initErr
}
d.Store.SetCache(m.ID, cache)
return nil
} }
// PostPersist implements Contract interface. // PostPersist implements Contract interface.
func (m *Management) PostPersist(ic *interop.Context) error { func (m *Management) PostPersist(ic *interop.Context) error {
m.mtx.Lock() cache := ic.DAO.Store.GetCache(m.ID).(*ManagementCache)
for h, cs := range m.contracts { cache.mtx.Lock()
defer cache.mtx.Unlock()
for h, cs := range cache.contracts {
if cs != nil { if cs != nil {
continue continue
} }
delete(m.nep11, h) delete(cache.nep11, h)
delete(m.nep17, h) delete(cache.nep17, h)
newCs, err := m.getContractFromDAO(ic.DAO, h) newCs, err := m.getContractFromDAO(ic.DAO, h)
if err != nil { if err != nil {
// Contract was destroyed. // Contract was destroyed.
delete(m.contracts, h) delete(cache.contracts, h)
continue continue
} }
m.updateContractCache(newCs) updateContractCache(cache, newCs)
} }
m.mtx.Unlock()
return nil return nil
} }
// GetNEP11Contracts returns hashes of all deployed contracts that support NEP-11 standard. The list // 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 updated every PostPersist, so until PostPersist is called, the result for the previous block
// is returned. // is returned.
func (m *Management) GetNEP11Contracts() []util.Uint160 { func (m *Management) GetNEP11Contracts(d *dao.Simple) []util.Uint160 {
m.mtx.RLock() cache := d.Store.GetCache(m.ID).(*ManagementCache)
result := make([]util.Uint160, 0, len(m.nep11)) cache.mtx.RLock()
for h := range m.nep11 { result := make([]util.Uint160, 0, len(cache.nep11))
for h := range cache.nep11 {
result = append(result, h) result = append(result, h)
} }
m.mtx.RUnlock() cache.mtx.RUnlock()
return result return result
} }
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP-17 standard. The list // 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 updated every PostPersist, so until PostPersist is called, the result for the previous block
// is returned. // is returned.
func (m *Management) GetNEP17Contracts() []util.Uint160 { func (m *Management) GetNEP17Contracts(d *dao.Simple) []util.Uint160 {
m.mtx.RLock() cache := d.Store.GetCache(m.ID).(*ManagementCache)
result := make([]util.Uint160, 0, len(m.nep17)) cache.mtx.RLock()
for h := range m.nep17 { result := make([]util.Uint160, 0, len(cache.nep17))
for h := range cache.nep17 {
result = append(result, h) result = append(result, h)
} }
m.mtx.RUnlock() cache.mtx.RUnlock()
return result return result
} }
@ -552,6 +567,13 @@ func (m *Management) GetNEP17Contracts() []util.Uint160 {
func (m *Management) Initialize(ic *interop.Context) error { func (m *Management) Initialize(ic *interop.Context) error {
setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee) setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee)
setIntWithKey(m.ID, ic.DAO, keyNextAvailableID, 1) 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 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 { if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
return err return err
} }
m.markUpdated(cs.Hash) m.markUpdated(d, cs.Hash)
if cs.UpdateCounter != 0 { // Update. if cs.UpdateCounter != 0 { // Update.
return nil return nil
} }

View file

@ -89,8 +89,10 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
d := dao.NewSimple(storage.NewMemoryStore(), false, false) d := dao.NewSimple(storage.NewMemoryStore(), false, false)
err := mgmt.Initialize(&interop.Context{DAO: d}) err := mgmt.Initialize(&interop.Context{DAO: d})
require.NoError(t, err) 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 // Deploy NEP-17 contract
script := []byte{byte(opcode.RET)} script := []byte{byte(opcode.RET)}
@ -108,11 +110,11 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// PostPersist is not yet called, thus no NEP-17 contracts are expected // 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 // Call PostPersist, check c1 contract hash is returned
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d})) 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 // Update contract
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{ manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
@ -124,9 +126,9 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// No changes expected before PostPersist call. // 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 // Call PostPersist, check c2 contract hash is returned
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d})) 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))
} }

View file

@ -108,7 +108,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee) absAmount := big.NewInt(tx.SystemFee + tx.NetworkFee)
g.burn(ic, tx.Sender(), absAmount) g.burn(ic, tx.Sender(), absAmount)
} }
validators := g.NEO.GetNextBlockValidatorsInternal() validators := g.NEO.GetNextBlockValidatorsInternal(ic.DAO)
primary := validators[ic.Block.PrimaryIndex].GetScriptHash() primary := validators[ic.Block.PrimaryIndex].GetScriptHash()
var netFee int64 var netFee int64
for _, tx := range ic.Block.Transactions { for _, tx := range ic.Block.Transactions {

View file

@ -35,6 +35,13 @@ type NEO struct {
GAS *GAS GAS *GAS
Policy *Policy 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. // gasPerBlock represents current value of generated gas per block.
// It is append-only and doesn't need to be copied when used. // It is append-only and doesn't need to be copied when used.
gasPerBlock atomic.Value 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 // It is set in state-modifying methods only and read in `PostPersist` thus is not protected
// by any mutex. // by any mutex.
gasPerVoteCache map[string]big.Int 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 ( const (
@ -129,13 +131,6 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
nep17.balFromBytes = n.balanceFromBytes nep17.balFromBytes = n.balanceFromBytes
n.nep17TokenNative = *nep17 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) err := n.initConfigCache(cfg)
if err != nil { if err != nil {
@ -213,9 +208,22 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return errors.New("already initialized") 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)] committee0 := n.standbyKeys[:n.cfg.GetCommitteeSize(ic.Block.Index)]
cvs := toKeysWithVotes(committee0) cvs := toKeysWithVotes(committee0)
err := n.updateCache(cvs, ic.Chain) err := n.updateCache(cache, cvs, ic.Chain)
if err != nil { if err != nil {
return err return err
} }
@ -233,13 +241,14 @@ func (n *NEO) Initialize(ic *interop.Context) error {
n.putGASRecord(ic.DAO, index, value) n.putGASRecord(ic.DAO, index, value)
gr := &gasRecord{{Index: index, GASPerBlock: *value}} gr := &gasRecord{{Index: index, GASPerBlock: *value}}
n.gasPerBlock.Store(*gr) cache.gasPerBlock.Store(*gr)
n.gasPerBlockChanged.Store(false) cache.gasPerBlockChanged.Store(false)
ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{}) ic.DAO.PutStorageItem(n.ID, []byte{prefixVotersCount}, state.StorageItem{})
setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice) setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice)
n.registerPrice.Store(int64(DefaultRegisterPrice)) cache.registerPrice.Store(int64(DefaultRegisterPrice))
n.registerPriceChanged.Store(false) cache.registerPriceChanged.Store(false)
return nil 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 // Cache initialisation should be done apart from Initialize because Initialize is
// called only when deploying native contracts. // called only when deploying native contracts.
func (n *NEO) InitializeCache(bc interop.Ledger, d *dao.Simple) error { 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{} var committee = keysWithVotes{}
si := d.GetStorageItem(n.ID, prefixCommittee) si := d.GetStorageItem(n.ID, prefixCommittee)
if err := committee.DecodeBytes(si); err != nil { 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 { if err := n.updateCache(cache, committee, bc); err != nil {
return err return fmt.Errorf("failed to update cache: %w", err)
} }
n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d)) cache.gasPerBlock.Store(n.getSortedGASRecordFromDAO(d))
n.gasPerBlockChanged.Store(false) cache.gasPerBlockChanged.Store(false)
d.Store.SetCache(n.ID, cache)
return nil return nil
} }
@ -270,27 +291,28 @@ func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error {
return err return err
} }
func (n *NEO) updateCache(cvs keysWithVotes, bc interop.Ledger) error { func (n *NEO) updateCache(cache *NeoCache, cvs keysWithVotes, bc interop.Ledger) error {
n.committee.Store(cvs) cache.committee.Store(cvs)
var committee = n.GetCommitteeMembers() var committee = getCommitteeMembers(cache)
script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy()) script, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee.Copy())
if err != nil { if err != nil {
return err 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() nextVals := committee[:n.cfg.GetNumOfCNs(bc.BlockHeight()+1)].Copy()
sort.Sort(nextVals) sort.Sort(nextVals)
n.nextValidators.Store(nextVals) cache.nextValidators.Store(nextVals)
return nil return nil
} }
func (n *NEO) updateCommittee(ic *interop.Context) error { func (n *NEO) updateCommittee(cache *NeoCache, ic *interop.Context) error {
votesChanged := n.votesChanged.Load().(bool) votesChanged := cache.votesChanged.Load().(bool)
if !votesChanged { if !votesChanged {
// We need to put in storage anyway, as it affects dumps // 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()) ic.DAO.PutStorageItem(n.ID, prefixCommittee, committee.Bytes())
return nil return nil
} }
@ -299,10 +321,10 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
if err != nil { if err != nil {
return err return err
} }
if err := n.updateCache(cvs, ic.Chain); err != nil { if err := n.updateCache(cache, cvs, ic.Chain); err != nil {
return err return err
} }
n.votesChanged.Store(false) cache.votesChanged.Store(false)
ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes()) ic.DAO.PutStorageItem(n.ID, prefixCommittee, cvs.Bytes())
return nil return nil
} }
@ -310,13 +332,14 @@ func (n *NEO) updateCommittee(ic *interop.Context) error {
// OnPersist implements Contract interface. // OnPersist implements Contract interface.
func (n *NEO) OnPersist(ic *interop.Context) error { func (n *NEO) OnPersist(ic *interop.Context) error {
if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) { if n.cfg.ShouldUpdateCommitteeAt(ic.Block.Index) {
oldKeys := n.nextValidators.Load().(keys.PublicKeys) cache := ic.DAO.Store.GetCache(n.ID).(*NeoCache)
oldCom := n.committee.Load().(keysWithVotes) oldKeys := cache.nextValidators.Load().(keys.PublicKeys)
oldCom := cache.committee.Load().(keysWithVotes)
if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) || if n.cfg.GetNumOfCNs(ic.Block.Index) != len(oldKeys) ||
n.cfg.GetCommitteeSize(ic.Block.Index) != len(oldCom) { 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 return err
} }
} }
@ -326,7 +349,8 @@ func (n *NEO) OnPersist(ic *interop.Context) error {
// PostPersist implements Contract interface. // PostPersist implements Contract interface.
func (n *NEO) PostPersist(ic *interop.Context) error { func (n *NEO) PostPersist(ic *interop.Context) error {
gas := n.GetGASPerBlock(ic.DAO, ic.Block.Index) 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) committeeSize := n.cfg.GetCommitteeSize(ic.Block.Index)
index := int(ic.Block.Index) % committeeSize index := int(ic.Block.Index) % committeeSize
committeeReward := new(big.Int).Mul(gas, bigCommitteeRewardRatio) 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, big.NewInt(int64(committeeSize+validatorsCount)))
voterReward.Div(voterReward, big100) voterReward.Div(voterReward, big100)
var cs = n.committee.Load().(keysWithVotes) var cs = cache.committee.Load().(keysWithVotes)
var key = make([]byte, 38) var key = make([]byte, 38)
for i := range cs { for i := range cs {
if cs[i].Votes.Sign() > 0 { 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) key = makeVoterKey([]byte(cs[i].Key), key)
var r *big.Int var r *big.Int
if g, ok := n.gasPerVoteCache[cs[i].Key]; ok { if g, ok := cache.gasPerVoteCache[cs[i].Key]; ok {
r = &g r = &g
} else { } else {
reward := n.getGASPerVote(ic.DAO, key[:34], []uint32{ic.Block.Index + 1}) 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) tmp.Add(tmp, r)
binary.BigEndian.PutUint32(key[34:], ic.Block.Index+1) 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)) ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp))
} }
} }
} }
if n.gasPerBlockChanged.Load().(bool) { if cache.gasPerBlockChanged.Load().(bool) {
n.gasPerBlock.Store(n.getSortedGASRecordFromDAO(ic.DAO)) cache.gasPerBlock.Store(n.getSortedGASRecordFromDAO(ic.DAO))
n.gasPerBlockChanged.Store(false) cache.gasPerBlockChanged.Store(false)
} }
if n.registerPriceChanged.Load().(bool) { if cache.registerPriceChanged.Load().(bool) {
p := getIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}) p := getIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice})
n.registerPrice.Store(p) cache.registerPrice.Store(p)
n.registerPriceChanged.Store(false) cache.registerPriceChanged.Store(false)
} }
return nil return nil
} }
@ -502,11 +526,12 @@ func (n *NEO) getSortedGASRecordFromDAO(d *dao.Simple) gasRecord {
// GetGASPerBlock returns gas generated for block with provided index. // GetGASPerBlock returns gas generated for block with provided index.
func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int { func (n *NEO) GetGASPerBlock(d *dao.Simple, index uint32) *big.Int {
cache := d.Store.GetCache(n.ID).(*NeoCache)
var gr gasRecord var gr gasRecord
if n.gasPerBlockChanged.Load().(bool) { if cache.gasPerBlockChanged.Load().(bool) {
gr = n.getSortedGASRecordFromDAO(d) gr = n.getSortedGASRecordFromDAO(d)
} else { } else {
gr = n.gasPerBlock.Load().(gasRecord) gr = cache.gasPerBlock.Load().(gasRecord)
} }
for i := len(gr) - 1; i >= 0; i-- { for i := len(gr) - 1; i >= 0; i-- {
if gr[i].Index <= index { 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. // GetCommitteeAddress returns address of the committee.
func (n *NEO) GetCommitteeAddress() util.Uint160 { func (n *NEO) GetCommitteeAddress(d *dao.Simple) util.Uint160 {
return n.committeeHash.Load().(util.Uint160) cache := d.Store.GetCache(n.ID).(*NeoCache)
return cache.committeeHash.Load().(util.Uint160)
} }
func (n *NEO) checkCommittee(ic *interop.Context) bool { 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 { if err != nil {
panic(err) panic(err)
} }
@ -547,7 +573,8 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) er
if !n.checkCommittee(ic) { if !n.checkCommittee(ic) {
return errors.New("invalid committee signature") 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) n.putGASRecord(ic.DAO, index, gas)
return nil return nil
} }
@ -557,8 +584,9 @@ func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackite
} }
func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 { func (n *NEO) getRegisterPriceInternal(d *dao.Simple) int64 {
if !n.registerPriceChanged.Load().(bool) { cache := d.Store.GetCache(n.ID).(*NeoCache)
return n.registerPrice.Load().(int64) if !cache.registerPriceChanged.Load().(bool) {
return cache.registerPrice.Load().(int64)
} }
return getIntWithKey(n.ID, d, []byte{prefixRegisterPrice}) 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()) 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{} 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 { if c.Registered || c.Votes.Sign() != 0 {
return false, nil 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. d.DeleteStorageItem(n.ID, append(voterKey, k...)) // d.Seek cuts prefix, thus need to append it again.
return true return true
}) })
delete(n.gasPerVoteCache, string(voterKey)) delete(cache.gasPerVoteCache, string(voterKey))
return true, nil 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") return nil, errors.New("negative value")
} }
var gr gasRecord var gr gasRecord
if !n.gasPerBlockChanged.Load().(bool) { cache := d.Store.GetCache(n.ID).(*NeoCache)
gr = n.gasPerBlock.Load().(gasRecord) if !cache.gasPerBlockChanged.Load().(bool) {
gr = cache.gasPerBlock.Load().(gasRecord)
} else { } else {
gr = n.getSortedGASRecordFromDAO(d) gr = n.getSortedGASRecordFromDAO(d)
} }
@ -717,10 +747,11 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
if si == nil { if si == nil {
return 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 := new(candidate).FromBytes(si)
c.Registered = false c.Registered = false
ok, err := n.dropCandidateIfZero(ic.DAO, pub, c) ok, err := n.dropCandidateIfZero(ic.DAO, cache, pub, c)
if ok { if ok {
return err 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). // 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). // 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 { 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 { if acc.VoteTo != nil {
key := makeValidatorKey(acc.VoteTo) key := makeValidatorKey(acc.VoteTo)
si := d.GetStorageItem(n.ID, key) 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 := new(candidate).FromBytes(si)
cd.Votes.Add(&cd.Votes, value) cd.Votes.Add(&cd.Votes, value)
if !isNewVote { if !isNewVote {
ok, err := n.dropCandidateIfZero(d, acc.VoteTo, cd) ok, err := n.dropCandidateIfZero(d, cache, acc.VoteTo, cd)
if ok { if ok {
return err return err
} }
} }
n.validators.Store(keys.PublicKeys(nil)) cache.validators.Store(keys.PublicKeys(nil))
return putConvertibleToDAO(n.ID, d, key, cd) return putConvertibleToDAO(n.ID, d, key, cd)
} }
return nil 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. // ComputeNextBlockValidators returns an actual list of current validators.
func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, error) { func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys.PublicKeys, error) {
numOfCNs := n.cfg.GetNumOfCNs(bc.BlockHeight() + 1) 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 return vals.Copy(), nil
} }
result, _, err := n.computeCommitteeMembers(bc, d) result, _, err := n.computeCommitteeMembers(bc, d)
@ -915,12 +948,12 @@ func (n *NEO) ComputeNextBlockValidators(bc interop.Ledger, d *dao.Simple) (keys
} }
result = result[:numOfCNs] result = result[:numOfCNs]
sort.Sort(result) sort.Sort(result)
n.validators.Store(result) cache.validators.Store(result)
return result, nil return result, nil
} }
func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item { func (n *NEO) getCommittee(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
pubs := n.GetCommitteeMembers() pubs := n.GetCommitteeMembers(ic.DAO)
sort.Sort(pubs) sort.Sort(pubs)
return pubsToArray(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. // GetCommitteeMembers returns public keys of nodes in committee using cached value.
func (n *NEO) GetCommitteeMembers() keys.PublicKeys { func (n *NEO) GetCommitteeMembers(d *dao.Simple) keys.PublicKeys {
var cvs = n.committee.Load().(keysWithVotes) 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 committee = make(keys.PublicKeys, len(cvs))
var err error var err error
for i := range committee { 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 { func (n *NEO) getNextBlockValidators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
result := n.GetNextBlockValidatorsInternal() result := n.GetNextBlockValidatorsInternal(ic.DAO)
return pubsToArray(result) return pubsToArray(result)
} }
// GetNextBlockValidatorsInternal returns next block validators. // GetNextBlockValidatorsInternal returns next block validators.
func (n *NEO) GetNextBlockValidatorsInternal() keys.PublicKeys { func (n *NEO) GetNextBlockValidatorsInternal(d *dao.Simple) keys.PublicKeys {
return n.nextValidators.Load().(keys.PublicKeys).Copy() cache := d.Store.GetCache(n.ID).(*NeoCache)
return cache.nextValidators.Load().(keys.PublicKeys).Copy()
} }
// BalanceOf returns native NEO token balance for the acc. // BalanceOf returns native NEO token balance for the acc.

View file

@ -32,7 +32,9 @@ type Notary struct {
GAS *GAS GAS *GAS
NEO *NEO NEO *NEO
Desig *Designate Desig *Designate
}
type NotaryCache struct {
lock sync.RWMutex lock sync.RWMutex
// isValid defies whether cached values were changed during the current // isValid defies whether cached values were changed during the current
// consensus iteration. If false, these values will be updated after // 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 { func (n *Notary) Initialize(ic *interop.Context) error {
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta)
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey) setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, defaultNotaryServiceFeePerKey)
n.isValid = true
n.maxNotValidBeforeDelta = defaultMaxNotValidBeforeDelta cache := &NotaryCache{
n.notaryServiceFeePerKey = defaultNotaryServiceFeePerKey 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 return nil
} }
@ -176,15 +192,16 @@ func (n *Notary) OnPersist(ic *interop.Context) error {
// PostPersist implements Contract interface. // PostPersist implements Contract interface.
func (n *Notary) PostPersist(ic *interop.Context) error { func (n *Notary) PostPersist(ic *interop.Context) error {
n.lock.Lock() cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache)
defer n.lock.Unlock() cache.lock.Lock()
if n.isValid { defer cache.lock.Unlock()
if cache.isValid {
return nil return nil
} }
n.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey)) cache.maxNotValidBeforeDelta = uint32(getIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey))
n.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey) cache.notaryServiceFeePerKey = getIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey)
n.isValid = true cache.isValid = true
return nil return nil
} }
@ -391,10 +408,11 @@ func (n *Notary) getMaxNotValidBeforeDelta(ic *interop.Context, _ []stackitem.It
// GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method. // GetMaxNotValidBeforeDelta is an internal representation of Notary getMaxNotValidBeforeDelta method.
func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 { func (n *Notary) GetMaxNotValidBeforeDelta(dao *dao.Simple) uint32 {
n.lock.RLock() cache := dao.Store.GetCache(n.ID).(*NotaryCache)
defer n.lock.RUnlock() cache.lock.RLock()
if n.isValid { defer cache.lock.RUnlock()
return n.maxNotValidBeforeDelta if cache.isValid {
return cache.maxNotValidBeforeDelta
} }
return uint32(getIntWithKey(n.ID, dao, maxNotValidBeforeDeltaKey)) 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) { if !n.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")
} }
n.lock.Lock() cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache)
defer n.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value)) setIntWithKey(n.ID, ic.DAO, maxNotValidBeforeDeltaKey, int64(value))
n.isValid = false cache.isValid = false
return stackitem.Null{} 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. // GetNotaryServiceFeePerKey is an internal representation of Notary getNotaryServiceFeePerKey method.
func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 { func (n *Notary) GetNotaryServiceFeePerKey(dao *dao.Simple) int64 {
n.lock.RLock() cache := dao.Store.GetCache(n.ID).(*NotaryCache)
defer n.lock.RUnlock() cache.lock.RLock()
if n.isValid { defer cache.lock.RUnlock()
return n.notaryServiceFeePerKey if cache.isValid {
return cache.notaryServiceFeePerKey
} }
return getIntWithKey(n.ID, dao, notaryServiceFeeKey) return getIntWithKey(n.ID, dao, notaryServiceFeeKey)
} }
@ -441,10 +461,11 @@ func (n *Notary) setNotaryServiceFeePerKey(ic *interop.Context, args []stackitem
if !n.NEO.checkCommittee(ic) { if !n.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")
} }
n.lock.Lock() cache := ic.DAO.Store.GetCache(n.ID).(*NotaryCache)
defer n.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value)) setIntWithKey(n.ID, ic.DAO, notaryServiceFeeKey, int64(value))
n.isValid = false cache.isValid = false
return stackitem.Null{} return stackitem.Null{}
} }

View file

@ -40,15 +40,17 @@ type Oracle struct {
Desig *Designate Desig *Designate
oracleScript []byte oracleScript []byte
requestPrice atomic.Value
requestPriceChanged atomic.Value
// Module is an oracle module capable of talking with the external world. // Module is an oracle module capable of talking with the external world.
Module atomic.Value Module atomic.Value
// newRequests contains new requests created during current block. // newRequests contains new requests created during current block.
newRequests map[uint64]*state.OracleRequest newRequests map[uint64]*state.OracleRequest
} }
type OracleCache struct {
requestPrice atomic.Value
requestPriceChanged atomic.Value
}
const ( const (
oracleContractID = -9 oracleContractID = -9
maxURLLength = 256 maxURLLength = 256
@ -121,8 +123,6 @@ func newOracle() *Oracle {
md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States) md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States)
o.AddMethod(md, desc) o.AddMethod(md, desc)
o.requestPriceChanged.Store(true)
return o return o
} }
@ -143,9 +143,10 @@ func (o *Oracle) OnPersist(ic *interop.Context) error {
// PostPersist represents `postPersist` method. // PostPersist represents `postPersist` method.
func (o *Oracle) PostPersist(ic *interop.Context) error { func (o *Oracle) PostPersist(ic *interop.Context) error {
p := o.getPriceInternal(ic.DAO) p := o.getPriceInternal(ic.DAO)
if o.requestPriceChanged.Load().(bool) { cache := ic.DAO.Store.GetCache(o.ID).(*OracleCache)
o.requestPrice.Store(p) if cache.requestPriceChanged.Load().(bool) {
o.requestPriceChanged.Store(false) cache.requestPrice.Store(p)
cache.requestPriceChanged.Store(false)
} }
var nodes keys.PublicKeys var nodes keys.PublicKeys
@ -220,11 +221,21 @@ func (o *Oracle) Metadata() *interop.ContractMD {
func (o *Oracle) Initialize(ic *interop.Context) error { func (o *Oracle) Initialize(ic *interop.Context) error {
setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0) setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice) 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 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 { func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
for i := range tx.Attributes { for i := range tx.Attributes {
if tx.Attributes[i].Type == transaction.OracleResponseT { 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 { func (o *Oracle) getPriceInternal(d *dao.Simple) int64 {
if !o.requestPriceChanged.Load().(bool) { cache := d.Store.GetCache(o.ID).(*OracleCache)
return o.requestPrice.Load().(int64) if !cache.requestPriceChanged.Load().(bool) {
return cache.requestPrice.Load().(int64)
} }
return getIntWithKey(o.ID, d, prefixRequestPrice) 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") panic("invalid committee signature")
} }
setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64()) 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{} return stackitem.Null{}
} }

View file

@ -53,6 +53,9 @@ var (
type Policy struct { type Policy struct {
interop.ContractMD interop.ContractMD
NEO *NEO NEO *NEO
}
type PolicyCache struct {
lock sync.RWMutex lock sync.RWMutex
// isValid defies whether cached values were changed during the current // isValid defies whether cached values were changed during the current
// consensus iteration. If false, these values will be updated after // 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, execFeeFactorKey, defaultExecFeeFactor)
setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice) setIntWithKey(p.ID, ic.DAO, storagePriceKey, DefaultStoragePrice)
p.isValid = true cache := &PolicyCache{}
p.execFeeFactor = defaultExecFeeFactor cache.isValid = true
p.feePerByte = defaultFeePerByte cache.execFeeFactor = defaultExecFeeFactor
p.maxVerificationGas = defaultMaxVerificationGas cache.feePerByte = defaultFeePerByte
p.storagePrice = DefaultStoragePrice cache.maxVerificationGas = defaultMaxVerificationGas
p.blockedAccounts = make([]util.Uint160, 0) cache.storagePrice = DefaultStoragePrice
cache.blockedAccounts = make([]util.Uint160, 0)
ic.DAO.Store.SetCache(p.ID, cache)
return nil 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. // OnPersist implements Contract interface.
func (p *Policy) OnPersist(ic *interop.Context) error { func (p *Policy) OnPersist(ic *interop.Context) error {
return nil return nil
@ -145,32 +184,14 @@ func (p *Policy) OnPersist(ic *interop.Context) error {
// PostPersist implements Contract interface. // PostPersist implements Contract interface.
func (p *Policy) PostPersist(ic *interop.Context) error { func (p *Policy) PostPersist(ic *interop.Context) error {
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
if p.isValid { defer cache.lock.Unlock()
if cache.isValid {
return nil return nil
} }
p.execFeeFactor = uint32(getIntWithKey(p.ID, ic.DAO, execFeeFactorKey)) return p.fillCacheFromDAO(cache, ic.DAO)
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
} }
// getFeePerByte is Policy contract method and returns required transaction's fee // 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. // GetFeePerByteInternal returns required transaction's fee per byte.
func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 { func (p *Policy) GetFeePerByteInternal(dao *dao.Simple) int64 {
p.lock.RLock() cache := dao.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.RUnlock() cache.lock.RLock()
if p.isValid { defer cache.lock.RUnlock()
return p.feePerByte if cache.isValid {
return cache.feePerByte
} }
return getIntWithKey(p.ID, dao, feePerByteKey) return getIntWithKey(p.ID, dao, feePerByteKey)
} }
// GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion. // GetMaxVerificationGas returns maximum gas allowed to be burned during verificaion.
func (p *Policy) GetMaxVerificationGas(_ *dao.Simple) int64 { func (p *Policy) GetMaxVerificationGas(dao *dao.Simple) int64 {
if p.isValid { cache := dao.Store.GetCache(p.ID).(*PolicyCache)
return p.maxVerificationGas cache.lock.RLock()
defer cache.lock.RUnlock()
if cache.isValid {
return cache.maxVerificationGas
} }
return defaultMaxVerificationGas return defaultMaxVerificationGas
} }
@ -203,10 +228,11 @@ func (p *Policy) getExecFeeFactor(ic *interop.Context, _ []stackitem.Item) stack
// GetExecFeeFactorInternal returns current execution fee factor. // GetExecFeeFactorInternal returns current execution fee factor.
func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 { func (p *Policy) GetExecFeeFactorInternal(d *dao.Simple) int64 {
p.lock.RLock() cache := d.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.RUnlock() cache.lock.RLock()
if p.isValid { defer cache.lock.RUnlock()
return int64(p.execFeeFactor) if cache.isValid {
return int64(cache.execFeeFactor)
} }
return getIntWithKey(p.ID, d, execFeeFactorKey) 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) { if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")
} }
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value)) setIntWithKey(p.ID, ic.DAO, execFeeFactorKey, int64(value))
p.isValid = false cache.isValid = false
return stackitem.Null{} 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. // IsBlockedInternal checks whether provided account is blocked.
func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool { func (p *Policy) IsBlockedInternal(dao *dao.Simple, hash util.Uint160) bool {
p.lock.RLock() cache := dao.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.RUnlock() cache.lock.RLock()
if p.isValid { defer cache.lock.RUnlock()
length := len(p.blockedAccounts) if cache.isValid {
length := len(cache.blockedAccounts)
i := sort.Search(length, func(i int) bool { 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 true
} }
return false return false
@ -256,10 +284,11 @@ func (p *Policy) getStoragePrice(ic *interop.Context, _ []stackitem.Item) stacki
// GetStoragePriceInternal returns current execution fee factor. // GetStoragePriceInternal returns current execution fee factor.
func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 { func (p *Policy) GetStoragePriceInternal(d *dao.Simple) int64 {
p.lock.RLock() cache := d.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.RUnlock() cache.lock.RLock()
if p.isValid { defer cache.lock.RUnlock()
return int64(p.storagePrice) if cache.isValid {
return int64(cache.storagePrice)
} }
return getIntWithKey(p.ID, d, storagePriceKey) 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) { if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")
} }
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value)) setIntWithKey(p.ID, ic.DAO, storagePriceKey, int64(value))
p.isValid = false cache.isValid = false
return stackitem.Null{} return stackitem.Null{}
} }
@ -288,10 +318,11 @@ func (p *Policy) setFeePerByte(ic *interop.Context, args []stackitem.Item) stack
if !p.NEO.checkCommittee(ic) { if !p.NEO.checkCommittee(ic) {
panic("invalid committee signature") panic("invalid committee signature")
} }
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
setIntWithKey(p.ID, ic.DAO, feePerByteKey, value) setIntWithKey(p.ID, ic.DAO, feePerByteKey, value)
p.isValid = false cache.isValid = false
return stackitem.Null{} return stackitem.Null{}
} }
@ -311,10 +342,11 @@ func (p *Policy) blockAccount(ic *interop.Context, args []stackitem.Item) stacki
return stackitem.NewBool(false) return stackitem.NewBool(false)
} }
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{}) ic.DAO.PutStorageItem(p.ID, key, state.StorageItem{})
p.isValid = false cache.isValid = false
return stackitem.NewBool(true) return stackitem.NewBool(true)
} }
@ -329,10 +361,11 @@ func (p *Policy) unblockAccount(ic *interop.Context, args []stackitem.Item) stac
return stackitem.NewBool(false) return stackitem.NewBool(false)
} }
key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...) key := append([]byte{blockedAccountPrefix}, hash.BytesBE()...)
p.lock.Lock() cache := ic.DAO.Store.GetCache(p.ID).(*PolicyCache)
defer p.lock.Unlock() cache.lock.Lock()
defer cache.lock.Unlock()
ic.DAO.DeleteStorageItem(p.ID, key) ic.DAO.DeleteStorageItem(p.ID, key)
p.isValid = false cache.isValid = false
return stackitem.NewBool(true) return stackitem.NewBool(true)
} }

View file

@ -15,6 +15,9 @@ import (
type MemCachedStore struct { type MemCachedStore struct {
MemoryStore MemoryStore
nativeCacheLock sync.RWMutex
nativeCache map[int32]*NativeCacheItem
private bool private bool
// plock protects Persist from double entrance. // plock protects Persist from double entrance.
plock sync.Mutex plock sync.Mutex
@ -22,6 +25,10 @@ type MemCachedStore struct {
ps Store ps Store
} }
type NativeCacheItem struct {
Value interface{}
}
type ( type (
// KeyValue represents key-value pair. // KeyValue represents key-value pair.
KeyValue struct { KeyValue struct {
@ -46,8 +53,15 @@ type (
// NewMemCachedStore creates a new MemCachedStore object. // NewMemCachedStore creates a new MemCachedStore object.
func NewMemCachedStore(lower Store) *MemCachedStore { 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{ return &MemCachedStore{
MemoryStore: *NewMemoryStore(), MemoryStore: *NewMemoryStore(),
nativeCache: cache,
ps: lower, ps: lower,
} }
} }
@ -55,8 +69,15 @@ func NewMemCachedStore(lower Store) *MemCachedStore {
// NewPrivateMemCachedStore creates a new private (unlocked) MemCachedStore object. // NewPrivateMemCachedStore creates a new private (unlocked) MemCachedStore object.
// Private cached stores are closed after Persist. // Private cached stores are closed after Persist.
func NewPrivateMemCachedStore(lower Store) *MemCachedStore { 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{ return &MemCachedStore{
MemoryStore: *NewMemoryStore(), MemoryStore: *NewMemoryStore(),
nativeCache: cache,
private: true, private: true,
ps: lower, ps: lower,
} }
@ -393,6 +414,25 @@ func (s *MemCachedStore) persist(isSync bool) (int, error) {
return keys, err 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 // Close implements Store interface, clears up memory and closes the lower layer
// Store. // Store.
func (s *MemCachedStore) Close() error { func (s *MemCachedStore) Close() error {

View file

@ -4,9 +4,11 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testchain" "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/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -798,13 +800,13 @@ func TestInvokeVerify(t *testing.T) {
var h uint32 = 1 var h uint32 = 1
_, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) _, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
require.Error(t, err) 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) { 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()}}) _, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
require.Error(t, err) 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) { 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) require.NoError(t, err)
_, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}) _, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
require.Error(t, err) 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) { t.Run("positive, with signer and witness", func(t *testing.T) {

View file

@ -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{}}) err = s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
if err != nil { 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 { } else {
ic.VM.LoadScriptWithFlags(script, callflag.All) ic.VM.LoadScriptWithFlags(script, callflag.All)