From 7e4b22f048de3be344f4422453dfa1c2daf94ccc Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 8 Apr 2024 14:29:44 +0300 Subject: [PATCH] native: make HF-specific MD cache less lazy Initialize all necessary HF-specific contract descriptors once during contract construction. Signed-off-by: Anna Shaleva --- pkg/config/hardfork.go | 15 ++++++++ pkg/config/hardfork_string.go | 8 ++--- pkg/core/interop/context.go | 65 +++++++++++++++++++++-------------- pkg/core/native/crypto.go | 1 + pkg/core/native/designate.go | 2 ++ pkg/core/native/ledger.go | 1 + pkg/core/native/management.go | 1 + pkg/core/native/native_gas.go | 1 + pkg/core/native/native_neo.go | 1 + pkg/core/native/notary.go | 1 + pkg/core/native/oracle.go | 1 + pkg/core/native/policy.go | 1 + pkg/core/native/std.go | 1 + 13 files changed, 70 insertions(+), 29 deletions(-) diff --git a/pkg/config/hardfork.go b/pkg/config/hardfork.go index ab9dbadac3..5e1539c12d 100644 --- a/pkg/config/hardfork.go +++ b/pkg/config/hardfork.go @@ -5,6 +5,13 @@ package config // Hardfork represents the application hard-fork identifier. 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 + const ( // HFAspidochelone represents hard-fork introduced in #2469 (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 == Hardfork(0) { + panic("unexpected call to Prev for the default hardfork") + } + return hf >> 1 +} + // IsHardforkValid denotes whether the provided string represents a valid // Hardfork name. func IsHardforkValid(s string) bool { diff --git a/pkg/config/hardfork_string.go b/pkg/config/hardfork_string.go index 48f2349f04..f3e22254b6 100644 --- a/pkg/config/hardfork_string.go +++ b/pkg/config/hardfork_string.go @@ -8,24 +8,24 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} + _ = x[HFDefault-0] _ = x[HFAspidochelone-1] _ = x[HFBasilisk-2] _ = x[hfLast-4] } const ( - _Hardfork_name_0 = "AspidocheloneBasilisk" + _Hardfork_name_0 = "HFDefaultAspidocheloneBasilisk" _Hardfork_name_1 = "hfLast" ) var ( - _Hardfork_index_0 = [...]uint8{0, 13, 21} + _Hardfork_index_0 = [...]uint8{0, 9, 22, 30} ) func (i Hardfork) String() string { switch { - case 1 <= i && i <= 2: - i -= 1 + case i <= 2: return _Hardfork_name_0[_Hardfork_index_0[i]:_Hardfork_index_0[i+1]] case i == 4: return _Hardfork_name_1 diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index ae4076dc2f..b8de8c0c6b 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -7,7 +7,6 @@ import ( "fmt" "sort" "strings" - "sync" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" @@ -204,10 +203,9 @@ type ContractMD struct { // by mutex. ActiveHFs map[config.Hardfork]struct{} - // mdCache contains hardfork-specific ready-to-use contract descriptors. This cache is lazy and thus, protected by - // mdCacheLock. - mdCacheLock sync.RWMutex - mdCache map[config.Hardfork]*HFSpecificContractMD + // mdCache contains hardfork-specific ready-to-use contract descriptors. This cache is initialized in the native + // contracts constructors, and acts as read-only during the whole node lifetime, thus not protected by mutex. + mdCache map[config.Hardfork]*HFSpecificContractMD // onManifestConstruction is a callback for manifest finalization. onManifestConstruction func(*manifest.Manifest) @@ -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 // 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 func (c *ContractMD) HFSpecificContractMD(hf *config.Hardfork) *HFSpecificContractMD { var key config.Hardfork if hf != nil { key = *hf } - c.mdCacheLock.RLock() - if md, ok := c.mdCache[key]; ok { - c.mdCacheLock.RUnlock() - return md + md, ok := c.mdCache[key] + if !ok { + 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)) } - c.mdCacheLock.RUnlock() - - md := c.buildHFSpecificMD(hf) return md } +// BuildHFSpecificMD generates and caches contract's descriptor for every known hardfork. +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 + } + fmt.Printf("Building MD for %s for %s\n", c.Name, hf) + c.buildHFSpecificMD(hf) + } + } +} + // buildHFSpecificMD builds hardfork-specific contract descriptor that includes methods and events active starting from -// the specified hardfork or older. -func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractMD { +// the specified hardfork or older. It also updates cache with the received value. +func (c *ContractMD) buildHFSpecificMD(hf config.Hardfork) { var ( abiMethods = make([]manifest.Method, 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() for i := range c.methods { 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 } @@ -289,7 +311,7 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM } for i := range c.events { 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 } @@ -314,10 +336,6 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM if c.onManifestConstruction != nil { c.onManifestConstruction(m) } - var key config.Hardfork - if hf != nil { - key = *hf - } md := &HFSpecificContractMD{ ContractBase: state.ContractBase{ ID: c.ID, @@ -329,10 +347,7 @@ func (c *ContractMD) buildHFSpecificMD(hf *config.Hardfork) *HFSpecificContractM Events: events, } - c.mdCacheLock.Lock() - c.mdCache[key] = md - c.mdCacheLock.Unlock() - return md + c.mdCache[hf] = md } // AddMethod adds a new method to a native contract. diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index 78eb3697ca..a03d2d552f 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -41,6 +41,7 @@ const cryptoContractID = -3 func newCrypto() *Crypto { c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)} + defer c.BuildHFSpecificMD(c.ActiveIn()) desc := newDescriptor("sha256", smartcontract.ByteArrayType, manifest.NewParameter("data", smartcontract.ByteArrayType)) diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index a6c0809114..64a2692f0c 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -104,6 +104,8 @@ func (s *Designate) isValidRole(r noderoles.Role) bool { func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.Role]keys.PublicKeys) *Designate { s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)} + defer s.BuildHFSpecificMD(s.ActiveIn()) + s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled s.initialNodeRoles = initialNodeRoles diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 0e8892d96f..79de8e832f 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -31,6 +31,7 @@ func newLedger() *Ledger { var l = &Ledger{ ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID), } + defer l.BuildHFSpecificMD(l.ActiveIn()) desc := newDescriptor("currentHash", smartcontract.Hash256Type) md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 46603ede89..3f2e2dd850 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -101,6 +101,7 @@ func newManagement() *Management { var m = &Management{ ContractMD: *interop.NewContractMD(nativenames.Management, ManagementContractID), } + defer m.BuildHFSpecificMD(m.ActiveIn()) desc := newDescriptor("getContract", smartcontract.ArrayType, manifest.NewParameter("hash", smartcontract.Hash160Type)) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index e5bde0addd..e5eb46a3e8 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -37,6 +37,7 @@ func newGAS(init int64, p2pSigExtensionsEnabled bool) *GAS { initialSupply: init, p2pSigExtensionsEnabled: p2pSigExtensionsEnabled, } + defer g.BuildHFSpecificMD(g.ActiveIn()) nep17 := newNEP17Native(nativenames.Gas, gasContractID) nep17.symbol = "GAS" diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index a90755c2d0..cc4e14daf1 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -172,6 +172,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte { // newNEO returns NEO native contract. func newNEO(cfg config.ProtocolConfiguration) *NEO { n := &NEO{} + defer n.BuildHFSpecificMD(n.ActiveIn()) nep17 := newNEP17Native(nativenames.Neo, neoContractID) nep17.symbol = "NEO" diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index c6343499e9..b93557e861 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -73,6 +73,7 @@ func copyNotaryCache(src, dst *NotaryCache) { // newNotary returns Notary native contract. func newNotary() *Notary { n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary, notaryContractID)} + defer n.BuildHFSpecificMD(n.ActiveIn()) desc := newDescriptor("onNEP17Payment", smartcontract.VoidType, manifest.NewParameter("from", smartcontract.Hash160Type), diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index ea46b84198..2e1309caca 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -118,6 +118,7 @@ func newOracle() *Oracle { ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID), newRequests: make(map[uint64]*state.OracleRequest), } + defer o.BuildHFSpecificMD(o.ActiveIn()) o.oracleScript = CreateOracleResponseScript(o.Hash) diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index b250aa4a29..77217d7327 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -105,6 +105,7 @@ func newPolicy(p2pSigExtensionsEnabled bool) *Policy { ContractMD: *interop.NewContractMD(nativenames.Policy, policyContractID), p2pSigExtensionsEnabled: p2pSigExtensionsEnabled, } + defer p.BuildHFSpecificMD(p.ActiveIn()) desc := newDescriptor("getFeePerByte", smartcontract.IntegerType) md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates) diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index 81e74b638d..caa6735e49 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -46,6 +46,7 @@ var ( func newStd() *Std { s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)} + defer s.BuildHFSpecificMD(s.ActiveIn()) desc := newDescriptor("serialize", smartcontract.ByteArrayType, manifest.NewParameter("item", smartcontract.AnyType))