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()
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()))

View file

@ -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)

View file

@ -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())
}

View file

@ -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
}

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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))
}

View file

@ -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 {

View file

@ -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.

View file

@ -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{}
}

View file

@ -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{}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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) {

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{}})
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)