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:
parent
d74dc368e0
commit
3a2e301267
13 changed files with 70 additions and 29 deletions
|
@ -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 // Default
|
||||
|
||||
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 == HFDefault {
|
||||
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 {
|
||||
|
|
|
@ -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 = "DefaultAspidocheloneBasilisk"
|
||||
_Hardfork_name_1 = "hfLast"
|
||||
)
|
||||
|
||||
var (
|
||||
_Hardfork_index_0 = [...]uint8{0, 13, 21}
|
||||
_Hardfork_index_0 = [...]uint8{0, 7, 20, 28}
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -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,9 +203,8 @@ 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 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.
|
||||
|
@ -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 this method for hardforks older than the contract
|
||||
// activation hardfork is a no-op.
|
||||
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()
|
||||
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))
|
||||
}
|
||||
return md
|
||||
}
|
||||
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
|
||||
}
|
||||
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.
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue