Skip to content

Commit

Permalink
native: make HF-specific MD cache less lazy
Browse files Browse the repository at this point in the history
Initialize all necessary HF-specific contract descriptors once during
contract construction.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
  • Loading branch information
AnnaShaleva committed Apr 9, 2024
1 parent f706d79 commit 7e4b22f
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 29 deletions.
15 changes: 15 additions & 0 deletions pkg/config/hardfork.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions pkg/config/hardfork_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 40 additions & 25 deletions pkg/core/interop/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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
}

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

Expand All @@ -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,
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions pkg/core/native/designate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/management.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/native_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/native_neo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/notary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/core/native/std.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 7e4b22f

Please sign in to comment.