diff --git a/pkg/config/hardfork.go b/pkg/config/hardfork.go new file mode 100644 index 000000000..ada169f68 --- /dev/null +++ b/pkg/config/hardfork.go @@ -0,0 +1,31 @@ +package config + +//go:generate stringer -type=Hardfork -linecomment + +// Hardfork represents the application hard-fork identifier. +type Hardfork byte + +const ( + // HF2712FixSyscallFees represents hard-fork introduced in #2469 (ported from + // https://github.com/neo-project/neo/pull/2712) changing the prices of + // System.Contract.CreateStandardAccount and + // System.Contract.CreateMultisigAccount interops. + HF2712FixSyscallFees Hardfork = 1 << iota // HF_2712_FixSyscallFees +) + +// hardforks holds a map of Hardfork string representation to its type. +var hardforks map[string]Hardfork + +func init() { + hardforks = make(map[string]Hardfork) + for _, hf := range []Hardfork{HF2712FixSyscallFees} { + hardforks[hf.String()] = hf + } +} + +// IsHardforkValid denotes whether the provided string represents a valid +// Hardfork name. +func IsHardforkValid(s string) bool { + _, ok := hardforks[s] + return ok +} diff --git a/pkg/config/hardfork_string.go b/pkg/config/hardfork_string.go new file mode 100644 index 000000000..c314b254c --- /dev/null +++ b/pkg/config/hardfork_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type Hardfork -linecomment ./pkg/config/hardfork.go"; DO NOT EDIT. + +package config + +import "strconv" + +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[HF2712FixSyscallFees-1] +} + +const _Hardfork_name = "HF_2712_FixSyscallFees" + +var _Hardfork_index = [...]uint8{0, 22} + +func (i Hardfork) String() string { + i -= 1 + if i >= Hardfork(len(_Hardfork_index)-1) { + return "Hardfork(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _Hardfork_name[_Hardfork_index[i]:_Hardfork_index[i+1]] +} diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index d05802a17..2685f18b2 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -23,6 +23,9 @@ type ( Magic netmode.Magic `yaml:"Magic"` MemPoolSize int `yaml:"MemPoolSize"` + // Hardforks is a map of hardfork names that enables version-specific application + // logic dependent on the specified height. + Hardforks map[string]uint32 `yaml:"Hardforks"` // InitialGASSupply is the amount of GAS generated in the genesis block. InitialGASSupply fixedn.Fixed8 `yaml:"InitialGASSupply"` // P2PNotaryRequestPayloadPoolSize specifies the memory pool size for P2PNotaryRequestPayloads. @@ -94,6 +97,11 @@ func (p *ProtocolConfiguration) Validate() error { return fmt.Errorf("NativeActivations configuration section contains unexpected native contract name: %s", name) } } + for name := range p.Hardforks { + if !IsHardforkValid(name) { + return fmt.Errorf("Hardforks configuration section contains unexpected hardfork: %s", name) + } + } if p.ValidatorsCount != 0 && len(p.ValidatorsHistory) != 0 { return errors.New("configuration should either have ValidatorsCount or ValidatorsHistory, not both") } diff --git a/pkg/config/protocol_config_test.go b/pkg/config/protocol_config_test.go index 2d3c2d811..c88cece9d 100644 --- a/pkg/config/protocol_config_test.go +++ b/pkg/config/protocol_config_test.go @@ -100,6 +100,12 @@ func TestProtocolConfigurationValidation(t *testing.T) { ValidatorsHistory: map[uint32]int{0: 4, 100: 4}, } require.Error(t, p.Validate()) + p = &ProtocolConfiguration{ + Hardforks: map[string]uint32{ + "HF_Unknown": 123, // Unknown hard-fork. + }, + } + require.Error(t, p.Validate()) p = &ProtocolConfiguration{ StandbyCommittee: []string{ "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 604fa2c62..bdd411d55 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -259,6 +259,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L cfg.NativeUpdateHistories = map[string][]uint32{} log.Info("NativeActivations are not set, using default values") } + if cfg.Hardforks == nil { + cfg.Hardforks = map[string]uint32{} + log.Info("Hardforks are not set, using default value") + } bc := &Blockchain{ config: cfg, dao: dao.NewSimple(s, cfg.StateRootInHeader, cfg.P2PSigExtensions), diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 5e7caf1fb..e6d2c30e6 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -48,6 +48,7 @@ type Context struct { Chain Ledger Container hash.Hashable Network uint32 + Hardforks map[string]uint32 Natives []Contract Trigger trigger.Type Block *block.Block @@ -71,9 +72,11 @@ func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, bas getContract func(*dao.Simple, util.Uint160) (*state.Contract, error), natives []Contract, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { dao := d.GetPrivate() + cfg := bc.GetConfig() return &Context{ Chain: bc, - Network: uint32(bc.GetConfig().Magic), + Network: uint32(cfg.Magic), + Hardforks: cfg.Hardforks, Natives: natives, Trigger: trigger, Block: block, @@ -368,3 +371,12 @@ func (ic *Context) GetBlock(hash util.Uint256) (*block.Block, error) { } return block, nil } + +// IsHardforkEnabled tells whether specified hard-fork enabled at the current context height. +func (ic *Context) IsHardforkEnabled(hf config.Hardfork) bool { + height, ok := ic.Hardforks[hf.String()] + if ok { + return ic.BlockHeight() >= height + } + return len(ic.Hardforks) == 0 // Enable each hard-fork by default. +}