native: make HF-specific MD cache less lazy

Initialize all necessary HF-specific contract descriptors once during
contract construction.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2024-04-08 14:29:44 +03:00
parent d74dc368e0
commit 3a2e301267
13 changed files with 70 additions and 29 deletions

View file

@ -5,6 +5,13 @@ package config
// Hardfork represents the application hard-fork identifier. // Hardfork represents the application hard-fork identifier.
type Hardfork byte type Hardfork byte
// HFDefault is a default value of Hardfork enum. It's a special constant
// aimed to denote the node code enabled by default starting from the
// genesis block. HFDefault is not a hard-fork, but this constant can be used for
// convenient hard-forks comparison and to refer to the default hard-fork-less
// node behaviour.
const HFDefault Hardfork = 0 // Default
const ( const (
// HFAspidochelone represents hard-fork introduced in #2469 (ported from // HFAspidochelone represents hard-fork introduced in #2469 (ported from
// https://github.com/neo-project/neo/pull/2712) and #2519 (ported from // https://github.com/neo-project/neo/pull/2712) and #2519 (ported from
@ -52,6 +59,14 @@ func (hf Hardfork) Cmp(other Hardfork) int {
} }
} }
// Prev returns the previous hardfork for the given one. Calling Prev for the default hardfork is a no-op.
func (hf Hardfork) Prev() Hardfork {
if hf == HFDefault {
panic("unexpected call to Prev for the default hardfork")
}
return hf >> 1
}
// IsHardforkValid denotes whether the provided string represents a valid // IsHardforkValid denotes whether the provided string represents a valid
// Hardfork name. // Hardfork name.
func IsHardforkValid(s string) bool { func IsHardforkValid(s string) bool {

View file

@ -8,24 +8,24 @@ func _() {
// An "invalid array index" compiler error signifies that the constant values have changed. // An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again. // Re-run the stringer command to generate them again.
var x [1]struct{} var x [1]struct{}
_ = x[HFDefault-0]
_ = x[HFAspidochelone-1] _ = x[HFAspidochelone-1]
_ = x[HFBasilisk-2] _ = x[HFBasilisk-2]
_ = x[hfLast-4] _ = x[hfLast-4]
} }
const ( const (
_Hardfork_name_0 = "AspidocheloneBasilisk" _Hardfork_name_0 = "DefaultAspidocheloneBasilisk"
_Hardfork_name_1 = "hfLast" _Hardfork_name_1 = "hfLast"
) )
var ( var (
_Hardfork_index_0 = [...]uint8{0, 13, 21} _Hardfork_index_0 = [...]uint8{0, 7, 20, 28}
) )
func (i Hardfork) String() string { func (i Hardfork) String() string {
switch { switch {
case 1 <= i && i <= 2: case i <= 2:
i -= 1
return _Hardfork_name_0[_Hardfork_index_0[i]:_Hardfork_index_0[i+1]] return _Hardfork_name_0[_Hardfork_index_0[i]:_Hardfork_index_0[i+1]]
case i == 4: case i == 4:
return _Hardfork_name_1 return _Hardfork_name_1

View file

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"sync"
"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"
@ -204,9 +203,8 @@ type ContractMD struct {
// by mutex. // by mutex.
ActiveHFs map[config.Hardfork]struct{} ActiveHFs map[config.Hardfork]struct{}
// mdCache contains hardfork-specific ready-to-use contract descriptors. This cache is lazy and thus, protected by // mdCache contains hardfork-specific ready-to-use contract descriptors. This cache is initialized in the native
// mdCacheLock. // contracts constructors, and acts as read-only during the whole node lifetime, thus not protected by mutex.
mdCacheLock sync.RWMutex
mdCache map[config.Hardfork]*HFSpecificContractMD mdCache map[config.Hardfork]*HFSpecificContractMD
// onManifestConstruction is a callback for manifest finalization. // onManifestConstruction is a callback for manifest finalization.
@ -238,26 +236,50 @@ func NewContractMD(name string, id int32, onManifestConstruction ...func(*manife
// HFSpecificContractMD returns hardfork-specific native contract metadata, i.e. with methods, events and script // HFSpecificContractMD returns hardfork-specific native contract metadata, i.e. with methods, events and script
// corresponding to the specified hardfork. If hardfork is not specified, then default metadata will be returned // corresponding to the specified hardfork. If hardfork is not specified, then default metadata will be returned
// (methods, events and script that are always active). // (methods, events and script that are always active). Calling this method for hardforks older than the contract
// activation hardfork is a no-op.
func (c *ContractMD) HFSpecificContractMD(hf *config.Hardfork) *HFSpecificContractMD { func (c *ContractMD) HFSpecificContractMD(hf *config.Hardfork) *HFSpecificContractMD {
var key config.Hardfork var key config.Hardfork
if hf != nil { if hf != nil {
key = *hf key = *hf
} }
c.mdCacheLock.RLock() md, ok := c.mdCache[key]
if md, ok := c.mdCache[key]; ok { if !ok {
c.mdCacheLock.RUnlock() panic(fmt.Errorf("native contract descriptor cache is not initialized: contract %s, hardfork %s", c.Hash.StringLE(), key))
}
if md == nil {
panic(fmt.Errorf("native contract descriptor cache is nil: contract %s, hardfork %s", c.Hash.StringLE(), key))
}
return md return md
} }
c.mdCacheLock.RUnlock()
md := c.buildHFSpecificMD(hf) // BuildHFSpecificMD generates and caches contract's descriptor for every known hardfork.
return md func (c *ContractMD) BuildHFSpecificMD(activeIn *config.Hardfork) {
var start config.Hardfork
if activeIn != nil {
start = *activeIn
}
for _, hf := range append([]config.Hardfork{config.HFDefault}, config.Hardforks...) {
switch {
case hf.Cmp(start) < 0:
continue
case hf.Cmp(start) == 0:
c.buildHFSpecificMD(hf)
default:
if _, ok := c.ActiveHFs[hf]; !ok {
// Intentionally omit HFSpecificContractMD structure copying since mdCache is read-only.
c.mdCache[hf] = c.mdCache[hf.Prev()]
continue
}
c.buildHFSpecificMD(hf)
}
}
} }
// buildHFSpecificMD builds hardfork-specific contract descriptor that includes methods and events active starting from // buildHFSpecificMD builds hardfork-specific contract descriptor that includes methods and events active starting from
// the specified hardfork or older. // the specified hardfork or older. It also updates cache with the received value.
func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractMD { func (c *ContractMD) buildHFSpecificMD(hf config.Hardfork) {
var ( var (
abiMethods = make([]manifest.Method, 0, len(c.methods)) abiMethods = make([]manifest.Method, 0, len(c.methods))
methods = make([]HFSpecificMethodAndPrice, 0, len(c.methods)) methods = make([]HFSpecificMethodAndPrice, 0, len(c.methods))
@ -267,7 +289,7 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
for i := range c.methods { for i := range c.methods {
m := c.methods[i] m := c.methods[i]
if !(m.ActiveFrom == nil || (hf != nil && (*m.ActiveFrom).Cmp(*hf) >= 0)) { if !(m.ActiveFrom == nil || (hf != config.HFDefault && (*m.ActiveFrom).Cmp(hf) >= 0)) {
continue continue
} }
@ -289,7 +311,7 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM
} }
for i := range c.events { for i := range c.events {
e := c.events[i] e := c.events[i]
if !(e.ActiveFrom == nil || (hf != nil && (*e.ActiveFrom).Cmp(*hf) >= 0)) { if !(e.ActiveFrom == nil || (hf != config.HFDefault && (*e.ActiveFrom).Cmp(hf) >= 0)) {
continue continue
} }
@ -314,10 +336,6 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM
if c.onManifestConstruction != nil { if c.onManifestConstruction != nil {
c.onManifestConstruction(m) c.onManifestConstruction(m)
} }
var key config.Hardfork
if hf != nil {
key = *hf
}
md := &HFSpecificContractMD{ md := &HFSpecificContractMD{
ContractBase: state.ContractBase{ ContractBase: state.ContractBase{
ID: c.ID, ID: c.ID,
@ -329,10 +347,7 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM
Events: events, Events: events,
} }
c.mdCacheLock.Lock() c.mdCache[hf] = md
c.mdCache[key] = md
c.mdCacheLock.Unlock()
return md
} }
// AddMethod adds a new method to a native contract. // AddMethod adds a new method to a native contract.

View file

@ -41,6 +41,7 @@ const cryptoContractID = -3
func newCrypto() *Crypto { func newCrypto() *Crypto {
c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)} c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)}
defer c.BuildHFSpecificMD(c.ActiveIn())
desc := newDescriptor("sha256", smartcontract.ByteArrayType, desc := newDescriptor("sha256", smartcontract.ByteArrayType,
manifest.NewParameter("data", smartcontract.ByteArrayType)) manifest.NewParameter("data", smartcontract.ByteArrayType))

View file

@ -104,6 +104,8 @@ func (s *Designate) isValidRole(r noderoles.Role) bool {
func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.Role]keys.PublicKeys) *Designate { func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.Role]keys.PublicKeys) *Designate {
s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)} s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)}
defer s.BuildHFSpecificMD(s.ActiveIn())
s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled
s.initialNodeRoles = initialNodeRoles s.initialNodeRoles = initialNodeRoles

View file

@ -31,6 +31,7 @@ func newLedger() *Ledger {
var l = &Ledger{ var l = &Ledger{
ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID), ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID),
} }
defer l.BuildHFSpecificMD(l.ActiveIn())
desc := newDescriptor("currentHash", smartcontract.Hash256Type) desc := newDescriptor("currentHash", smartcontract.Hash256Type)
md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates) md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates)

View file

@ -101,6 +101,7 @@ func newManagement() *Management {
var m = &Management{ var m = &Management{
ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID),
} }
defer m.BuildHFSpecificMD(m.ActiveIn())
desc := newDescriptor("getContract", smartcontract.ArrayType, desc := newDescriptor("getContract", smartcontract.ArrayType,
manifest.NewParameter("hash", smartcontract.Hash160Type)) manifest.NewParameter("hash", smartcontract.Hash160Type))

View file

@ -37,6 +37,7 @@ func newGAS(init int64, p2pSigExtensionsEnabled bool) *GAS {
initialSupply: init, initialSupply: init,
p2pSigExtensionsEnabled: p2pSigExtensionsEnabled, p2pSigExtensionsEnabled: p2pSigExtensionsEnabled,
} }
defer g.BuildHFSpecificMD(g.ActiveIn())
nep17 := newNEP17Native(nativenames.Gas, gasContractID) nep17 := newNEP17Native(nativenames.Gas, gasContractID)
nep17.symbol = "GAS" nep17.symbol = "GAS"

View file

@ -172,6 +172,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
// newNEO returns NEO native contract. // newNEO returns NEO native contract.
func newNEO(cfg config.ProtocolConfiguration) *NEO { func newNEO(cfg config.ProtocolConfiguration) *NEO {
n := &NEO{} n := &NEO{}
defer n.BuildHFSpecificMD(n.ActiveIn())
nep17 := newNEP17Native(nativenames.Neo, neoContractID) nep17 := newNEP17Native(nativenames.Neo, neoContractID)
nep17.symbol = "NEO" nep17.symbol = "NEO"

View file

@ -73,6 +73,7 @@ func copyNotaryCache(src, dst *NotaryCache) {
// newNotary returns Notary native contract. // newNotary returns Notary native contract.
func newNotary() *Notary { func newNotary() *Notary {
n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)} n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)}
defer n.BuildHFSpecificMD(n.ActiveIn())
desc := newDescriptor("onNEP17Payment", smartcontract.VoidType, desc := newDescriptor("onNEP17Payment", smartcontract.VoidType,
manifest.NewParameter("from", smartcontract.Hash160Type), manifest.NewParameter("from", smartcontract.Hash160Type),

View file

@ -118,6 +118,7 @@ func newOracle() *Oracle {
ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID), ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID),
newRequests: make(map[uint64]*state.OracleRequest), newRequests: make(map[uint64]*state.OracleRequest),
} }
defer o.BuildHFSpecificMD(o.ActiveIn())
o.oracleScript = CreateOracleResponseScript(o.Hash) o.oracleScript = CreateOracleResponseScript(o.Hash)

View file

@ -105,6 +105,7 @@ func newPolicy(p2pSigExtensionsEnabled bool) *Policy {
ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID), ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID),
p2pSigExtensionsEnabled: p2pSigExtensionsEnabled, p2pSigExtensionsEnabled: p2pSigExtensionsEnabled,
} }
defer p.BuildHFSpecificMD(p.ActiveIn())
desc := newDescriptor("getFeePerByte", smartcontract.IntegerType) desc := newDescriptor("getFeePerByte", smartcontract.IntegerType)
md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates) md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates)

View file

@ -46,6 +46,7 @@ var (
func newStd() *Std { func newStd() *Std {
s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)} s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)}
defer s.BuildHFSpecificMD(s.ActiveIn())
desc := newDescriptor("serialize", smartcontract.ByteArrayType, desc := newDescriptor("serialize", smartcontract.ByteArrayType,
manifest.NewParameter("item", smartcontract.AnyType)) manifest.NewParameter("item", smartcontract.AnyType))