From 58102a9a803099e5bfca6d9b92d8e69c19b540c2 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 21 Nov 2023 12:35:18 +0300 Subject: [PATCH] *: move NativeUpdateHistory logic under Hardforks management Close #3196. Signed-off-by: Anna Shaleva --- config/protocol.mainnet.neofs.yml | 11 --- config/protocol.mainnet.yml | 10 --- config/protocol.privnet.docker.four.yml | 10 --- config/protocol.privnet.docker.one.yml | 10 --- config/protocol.privnet.docker.single.yml | 10 --- config/protocol.privnet.docker.three.yml | 10 --- config/protocol.privnet.docker.two.yml | 10 --- config/protocol.privnet.yml | 10 --- config/protocol.testnet.neofs.yml | 11 --- config/protocol.testnet.yml | 10 --- config/protocol.unit_testnet.single.yml | 11 --- config/protocol.unit_testnet.yml | 11 --- docs/node-configuration.md | 5 +- docs/notary.md | 16 +---- pkg/config/protocol_config.go | 20 ------ pkg/config/protocol_config_test.go | 17 ----- pkg/config/testdata/protocol.test.yml | 4 -- pkg/core/blockchain.go | 40 ++++++----- pkg/core/blockchain_neotest_test.go | 40 ++--------- pkg/core/interop/context.go | 17 +++-- pkg/core/native/contract.go | 8 --- pkg/core/native/crypto.go | 6 ++ pkg/core/native/designate.go | 6 ++ pkg/core/native/interop.go | 36 ++++++---- pkg/core/native/invocation_test.go | 84 ++++++++++++----------- pkg/core/native/ledger.go | 6 ++ pkg/core/native/management.go | 12 +++- pkg/core/native/native_gas.go | 6 ++ pkg/core/native/native_neo.go | 5 ++ pkg/core/native/notary.go | 6 ++ pkg/core/native/oracle.go | 6 ++ pkg/core/native/policy.go | 6 ++ pkg/core/native/std.go | 5 ++ pkg/services/rpcsrv/server_test.go | 1 - 34 files changed, 170 insertions(+), 306 deletions(-) delete mode 100644 pkg/config/testdata/protocol.test.yml diff --git a/config/protocol.mainnet.neofs.yml b/config/protocol.mainnet.neofs.yml index 7097dddf4..5d5ba25e5 100644 --- a/config/protocol.mainnet.neofs.yml +++ b/config/protocol.mainnet.neofs.yml @@ -26,17 +26,6 @@ ProtocolConfiguration: Hardforks: Aspidochelone: 3000000 Basilisk: 4500000 - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] - Notary: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index 7bdc12fdc..87320c269 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -38,16 +38,6 @@ ProtocolConfiguration: Hardforks: Aspidochelone: 1730000 Basilisk: 4120000 - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.docker.four.yml b/config/protocol.privnet.docker.four.yml index f4ce1a16b..d1d18296f 100644 --- a/config/protocol.privnet.docker.four.yml +++ b/config/protocol.privnet.docker.four.yml @@ -16,16 +16,6 @@ ProtocolConfiguration: - node_four:20336 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.docker.one.yml b/config/protocol.privnet.docker.one.yml index e0d0f9a35..5c0873f10 100644 --- a/config/protocol.privnet.docker.one.yml +++ b/config/protocol.privnet.docker.one.yml @@ -16,16 +16,6 @@ ProtocolConfiguration: - node_four:20336 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.docker.single.yml b/config/protocol.privnet.docker.single.yml index f5a119414..cbc7bbd35 100644 --- a/config/protocol.privnet.docker.single.yml +++ b/config/protocol.privnet.docker.single.yml @@ -10,16 +10,6 @@ ProtocolConfiguration: - node_single:20333 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.docker.three.yml b/config/protocol.privnet.docker.three.yml index 634647c9e..e01ce168f 100644 --- a/config/protocol.privnet.docker.three.yml +++ b/config/protocol.privnet.docker.three.yml @@ -16,16 +16,6 @@ ProtocolConfiguration: - node_four:20336 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.docker.two.yml b/config/protocol.privnet.docker.two.yml index f63158a6a..39825d613 100644 --- a/config/protocol.privnet.docker.two.yml +++ b/config/protocol.privnet.docker.two.yml @@ -16,16 +16,6 @@ ProtocolConfiguration: - node_four:20336 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.privnet.yml b/config/protocol.privnet.yml index 39464eb9c..1d1049813 100644 --- a/config/protocol.privnet.yml +++ b/config/protocol.privnet.yml @@ -16,16 +16,6 @@ ProtocolConfiguration: - localhost:20336 VerifyTransactions: true P2PSigExtensions: false - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.testnet.neofs.yml b/config/protocol.testnet.neofs.yml index 45b5d9c33..efd9c7a59 100644 --- a/config/protocol.testnet.neofs.yml +++ b/config/protocol.testnet.neofs.yml @@ -24,17 +24,6 @@ ProtocolConfiguration: - morph7.t5.fs.neo.org:50333 VerifyTransactions: true P2PSigExtensions: true - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] - Notary: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index c2148b447..09d08d779 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -41,16 +41,6 @@ ProtocolConfiguration: Hardforks: Aspidochelone: 210000 Basilisk: 2680000 - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ApplicationConfiguration: SkipBlockVerification: false diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml index 0fd69a69f..a7b08ce06 100644 --- a/config/protocol.unit_testnet.single.yml +++ b/config/protocol.unit_testnet.single.yml @@ -8,17 +8,6 @@ ProtocolConfiguration: ValidatorsCount: 1 VerifyTransactions: true P2PSigExtensions: true - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] - Notary: [0] Hardforks: Aspidochelone: 25 diff --git a/config/protocol.unit_testnet.yml b/config/protocol.unit_testnet.yml index d92fcd225..97170f502 100644 --- a/config/protocol.unit_testnet.yml +++ b/config/protocol.unit_testnet.yml @@ -17,17 +17,6 @@ ProtocolConfiguration: - 127.0.0.1:20336 VerifyTransactions: true P2PSigExtensions: true - NativeActivations: - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] - Notary: [0] Hardforks: Aspidochelone: 25 diff --git a/docs/node-configuration.md b/docs/node-configuration.md index c153250f9..d80ed5267 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -334,7 +334,6 @@ protocol-related settings described in the table below. | MaxTransactionsPerBlock | `uint16` | `512` | Maximum number of transactions per block. | | MaxValidUntilBlockIncrement | `uint32` | `5760` | Upper height increment limit for transaction's ValidUntilBlock field value relative to the current blockchain height, exceeding which a transaction will fail validation. It is set to estimated daily number of blocks with 15s interval by default. | | MemPoolSize | `int` | `50000` | Size of the node's memory pool where transactions are stored before they are added to block. | -| NativeActivations | `map[string][]uint32` | ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0] | The list of histories of native contracts updates. Each list item shod be presented as a known native contract name with the corresponding list of chain's heights. The contract is not active until chain reaches the first height value specified in the list. | `Notary` is supported. | | P2PNotaryRequestPayloadPoolSize | `int` | `1000` | Size of the node's P2P Notary request payloads memory pool where P2P Notary requests are stored before main or fallback transaction is completed and added to the chain.
This option is valid only if `P2PSigExtensions` are enabled. | Not supported by the C# node, thus may affect heterogeneous networks functionality. | | P2PSigExtensions | `bool` | `false` | Enables following additional Notary service related logic:
• Transaction attribute `NotaryAssisted`
• Network payload of the `P2PNotaryRequest` type
• Native `Notary` contract
• Notary node module | Not supported by the C# node, thus may affect heterogeneous networks functionality. | | P2PStateExchangeExtensions | `bool` | `false` | Enables the following P2P MPT state data exchange logic:
• `StateSyncInterval` protocol setting
• P2P commands `GetMPTDataCMD` and `MPTDataCMD` | Not supported by the C# node, thus may affect heterogeneous networks functionality. Can be supported either on MPT-complete node (`KeepOnlyLatestState`=`false`) or on light GC-enabled node (`RemoveUntraceableBlocks=true`) in which case `KeepOnlyLatestState` setting doesn't change the behavior, an appropriate set of MPTs is always stored (see `RemoveUntraceableBlocks`). | @@ -383,9 +382,9 @@ where: Note that Roles is a NeoGo extension that isn't supported by the NeoC# node and must be disabled on the public Neo N3 networks. Roles extension is compatible - with NativeUpdateHistory setting, which means that specified roles will be set + with Hardforks setting, which means that specified roles will be set only during native RoleManagement contract initialisation (which may be - performed in some non-genesis block). By default, no roles are designated. + performed in some non-genesis hardfork). By default, no roles are designated. - `Transaction` is a container for transaction script that should be deployed in the genesis block if provided. `Transaction` includes `Script` which is a diff --git a/docs/notary.md b/docs/notary.md index bd68bef1c..7a4b63ad3 100644 --- a/docs/notary.md +++ b/docs/notary.md @@ -171,9 +171,8 @@ verify and broadcast `P2PNotaryRequest` P2P payloads, properly initialize native Notary contract and designate `P2PNotary` node role in RoleManagement native contract. -If you use custom `NativeActivations` subsection of the `ProtocolConfiguration` -section in your node config, specify the height of the Notary contract -activation, e.g. `0`. +Currently, Notary contract activation height is not configurable and is always +set to 0 (if `P2PSigExtensions` are enabled). Note, that even if `P2PSigExtensions` config subsection enables notary-related logic in the network, it still does not turn your node into notary service node. @@ -184,17 +183,6 @@ To enable notary service node functionality refer to the ``` P2PSigExtensions: true - NativeActivations: - Notary: [0] - ContractManagement: [0] - StdLib: [0] - CryptoLib: [0] - LedgerContract: [0] - NeoToken: [0] - GasToken: [0] - PolicyContract: [0] - RoleManagement: [0] - OracleContract: [0] ``` diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 2e9f8aad8..88e43d8ad 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -7,7 +7,6 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" ) @@ -44,8 +43,6 @@ type ( // exceeding that a transaction should fail validation. It is set to estimated daily number // of blocks with 15s interval. MaxValidUntilBlockIncrement uint32 `yaml:"MaxValidUntilBlockIncrement"` - // NativeUpdateHistories is a list of histories of native contracts updates. - NativeUpdateHistories map[string][]uint32 `yaml:"NativeActivations"` // P2PSigExtensions enables additional signature-related logic. P2PSigExtensions bool `yaml:"P2PSigExtensions"` // P2PStateExchangeExtensions enables additional P2P MPT state data exchange logic. @@ -86,11 +83,6 @@ func (p *ProtocolConfiguration) Validate() error { if p.TimePerBlock%time.Millisecond != 0 { return errors.New("TimePerBlock must be an integer number of milliseconds") } - for name := range p.NativeUpdateHistories { - if !nativenames.IsValid(name) { - 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) @@ -237,7 +229,6 @@ func (p *ProtocolConfiguration) Equals(o *ProtocolConfiguration) bool { p.VerifyTransactions != o.VerifyTransactions || len(p.CommitteeHistory) != len(o.CommitteeHistory) || len(p.Hardforks) != len(o.Hardforks) || - len(p.NativeUpdateHistories) != len(o.NativeUpdateHistories) || len(p.SeedList) != len(o.SeedList) || len(p.StandbyCommittee) != len(o.StandbyCommittee) || len(p.ValidatorsHistory) != len(o.ValidatorsHistory) { @@ -255,17 +246,6 @@ func (p *ProtocolConfiguration) Equals(o *ProtocolConfiguration) bool { return false } } - for k, v := range p.NativeUpdateHistories { - vo := o.NativeUpdateHistories[k] - if len(v) != len(vo) { - return false - } - for i := range v { - if v[i] != vo[i] { - return false - } - } - } for i := range p.SeedList { if p.SeedList[i] != o.SeedList[i] { return false diff --git a/pkg/config/protocol_config_test.go b/pkg/config/protocol_config_test.go index 1b17fec49..91a11be96 100644 --- a/pkg/config/protocol_config_test.go +++ b/pkg/config/protocol_config_test.go @@ -28,12 +28,6 @@ func TestProtocolConfigurationValidation(t *testing.T) { ValidatorsCount: 1, } require.Error(t, p.Validate()) - p = &ProtocolConfiguration{ - NativeUpdateHistories: map[string][]uint32{ - "someContract": {0, 10}, - }, - } - require.Error(t, p.Validate()) p = &ProtocolConfiguration{ StandbyCommittee: []string{ "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", @@ -247,17 +241,6 @@ func TestProtocolConfigurationEquals(t *testing.T) { p.Hardforks = nil o.Hardforks = nil - p.NativeUpdateHistories = map[string][]uint32{"Contract": {1, 2, 3}} - o.NativeUpdateHistories = map[string][]uint32{"Contract": {1, 2, 3}} - require.True(t, p.Equals(o)) - p.NativeUpdateHistories["Contract"] = []uint32{1, 2, 3, 4} - require.False(t, p.Equals(o)) - p.NativeUpdateHistories["Contract"] = []uint32{1, 2, 4} - require.False(t, p.Equals(o)) - - p.NativeUpdateHistories = nil - o.NativeUpdateHistories = nil - p.SeedList = []string{"url1", "url2"} o.SeedList = []string{"url1", "url2"} require.True(t, p.Equals(o)) diff --git a/pkg/config/testdata/protocol.test.yml b/pkg/config/testdata/protocol.test.yml deleted file mode 100644 index 31619903f..000000000 --- a/pkg/config/testdata/protocol.test.yml +++ /dev/null @@ -1,4 +0,0 @@ -ProtocolConfiguration: - NativeActivations: - ContractManagement: [0] - UnexpectedContractName: [0] diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 5882da12a..80b2978fa 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -283,10 +283,6 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger) (*Bl zap.Int("StateSyncInterval", cfg.StateSyncInterval)) } } - if len(cfg.NativeUpdateHistories) == 0 { - cfg.NativeUpdateHistories = map[string][]uint32{} - log.Info("NativeActivations are not set, using default values") - } if cfg.Hardforks == nil { cfg.Hardforks = map[string]uint32{} for _, hf := range config.Hardforks { @@ -485,8 +481,8 @@ func (bc *Blockchain) init() error { for _, c := range bc.contracts.Contracts { md := c.Metadata() storedCS := bc.GetContractState(md.Hash) - history := md.UpdateHistory - if len(history) == 0 || history[0] > bHeight { + // Check that contract was deployed. + if !bc.isHardforkEnabled(c.ActiveIn(), bHeight) { if storedCS != nil { return fmt.Errorf("native contract %s is already stored, but marked as inactive for height %d in config", md.Name, bHeight) } @@ -1022,19 +1018,31 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage) func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error { for _, c := range bc.contracts.Contracts { - for _, h := range c.Metadata().UpdateHistory { - if blockHeight >= h { // check that contract was deployed. - err := c.InitializeCache(blockHeight, d) - if err != nil { - return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err) - } - break - } + // Check that contract was deployed. + if !bc.isHardforkEnabled(c.ActiveIn(), blockHeight) { + continue + } + err := c.InitializeCache(blockHeight, d) + if err != nil { + return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err) } } return nil } +// isHardforkEnabled returns true if the specified hardfork is enabled at the +// given height. nil hardfork is treated as always enabled. +func (bc *Blockchain) isHardforkEnabled(hf *config.Hardfork, blockHeight uint32) bool { + hfs := bc.config.Hardforks + if hf != nil { + start, ok := hfs[hf.String()] + if !ok || start < blockHeight { + return false + } + } + return true +} + // Run runs chain loop, it needs to be run as goroutine and executing it is // critical for correct Blockchain operation. func (bc *Blockchain) Run() { @@ -2994,8 +3002,8 @@ func (bc *Blockchain) GetMaxNotValidBeforeDelta() (uint32, error) { if !bc.config.P2PSigExtensions { panic("disallowed call to Notary") // critical error, thus panic. } - if bc.contracts.Notary.Metadata().UpdateHistory[0] > bc.BlockHeight() { - return 0, fmt.Errorf("native Notary is active starting from %d", bc.contracts.Notary.Metadata().UpdateHistory[0]) + if !bc.isHardforkEnabled(bc.contracts.Notary.ActiveIn(), bc.BlockHeight()) { + return 0, fmt.Errorf("native Notary is active starting from %s", bc.contracts.Notary.ActiveIn().String()) } return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao), nil } diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index fe107ec5c..877610cd7 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -237,26 +237,6 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) { require.True(t, strings.Contains(err.Error(), "can't init cache for Management native contract"), err) }) */ - t.Run("invalid native contract deactivation", func(t *testing.T) { - ps = newPS(t) - _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.Blockchain) { - customConfig(c) - c.NativeUpdateHistories = map[string][]uint32{ - nativenames.Policy: {0}, - nativenames.Neo: {0}, - nativenames.Gas: {0}, - nativenames.Designation: {0}, - nativenames.StdLib: {0}, - nativenames.Management: {0}, - nativenames.Oracle: {0}, - nativenames.Ledger: {0}, - nativenames.Notary: {0}, - nativenames.CryptoLib: {h + 10}, - } - }, ps) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is already stored, but marked as inactive for height %d in config", nativenames.CryptoLib, h)), err) - }) t.Run("invalid native contract activation", func(t *testing.T) { ps = newPS(t) @@ -339,6 +319,7 @@ func TestBlockchain_InitializeNeoCache_Bug3181(t *testing.T) { // This test enables Notary native contract at non-zero height and checks that no // Notary cache initialization is performed before that height on node restart. +/* func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) { const notaryEnabledHeight = 3 ps, path := newLevelDBForTestingWithPath(t, "") @@ -399,6 +380,7 @@ func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) { _, err = e.Chain.GetMaxNotValidBeforeDelta() require.NoError(t, err) } +*/ func TestBlockchain_AddHeaders(t *testing.T) { bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) { @@ -1116,25 +1098,13 @@ func TestBlockchain_MPTDeleteNoKey(t *testing.T) { cValidatorInvoker.Invoke(t, stackitem.Null{}, "delValue", "non-existent-key") } -// Test that UpdateHistory is added to ProtocolConfiguration for all native contracts -// for all default configurations. If UpdateHistory is not added to config, then -// native contract is disabled. It's easy to forget about config while adding new -// native contract. -func TestConfigNativeUpdateHistory(t *testing.T) { +// Test that all default configurations are loadable. +func TestConfig_LoadDefaultConfigs(t *testing.T) { var prefixPath = filepath.Join("..", "..", "config") check := func(t *testing.T, cfgFileSuffix any) { cfgPath := filepath.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix)) - cfg, err := config.LoadFile(cfgPath) + _, err := config.LoadFile(cfgPath) require.NoError(t, err, fmt.Errorf("failed to load %s", cfgPath)) - natives := native.NewContracts(cfg.ProtocolConfiguration) - assert.Equal(t, len(natives.Contracts), - len(cfg.ProtocolConfiguration.NativeUpdateHistories), - fmt.Errorf("protocol configuration file %s: extra or missing NativeUpdateHistory in NativeActivations section", cfgPath)) - for _, c := range natives.Contracts { - assert.NotNil(t, cfg.ProtocolConfiguration.NativeUpdateHistories[c.Metadata().Name], - fmt.Errorf("protocol configuration file %s: configuration for %s native contract is missing in NativeActivations section; "+ - "edit the test if the contract should be disabled", cfgPath, c.Metadata().Name)) - } } testCases := []any{ netmode.MainNet, diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0aa71c29a..cb3d94fc3 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -153,6 +153,9 @@ type MethodAndPrice struct { // Contract is an interface for all native contracts. type Contract interface { Initialize(*Context) error + // ActiveIn returns the hardfork native contract is active from or nil in case + // it's always active. + ActiveIn() *config.Hardfork // InitializeCache aimed to initialize contract's cache when the contract has // been deployed, but in-memory cached data were lost due to the node reset. // It should be called each time after node restart iff the contract was @@ -269,12 +272,6 @@ func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) { }) } -// IsActive returns true if the contract was deployed by the specified height. -func (c *ContractMD) IsActive(height uint32) bool { - history := c.UpdateHistory - return len(history) != 0 && history[0] <= height -} - // Sort sorts interop functions by id. func Sort(fs []Function) { sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID }) @@ -411,6 +408,14 @@ func (ic *Context) IsHardforkEnabled(hf config.Hardfork) bool { return false } +// IsHardforkActivation denotes whether current block height is the height of +// specified hardfork activation. +func (ic *Context) IsHardforkActivation(hf config.Hardfork) bool { + // Completely rely on proper hardforks initialisation made by core.NewBlockchain. + height, ok := ic.Hardforks[hf.String()] + return ok && ic.Block.Index == height +} + // AddNotification creates notification event and appends it to the notification list. func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) { ic.Notifications = append(ic.Notifications, state.NotificationEvent{ diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 19321f887..8c20bd6c1 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -109,14 +109,6 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts { cs.Contracts = append(cs.Contracts, notary) } - setDefaultHistory := len(cfg.NativeUpdateHistories) == 0 - for _, c := range cs.Contracts { - var history = []uint32{0} - if !setDefaultHistory { - history = cfg.NativeUpdateHistories[c.Metadata().Name] - } - c.Metadata().NativeContract.UpdateHistory = history - } return cs } diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index d0d830f88..dc025d54a 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -9,6 +9,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -308,3 +309,8 @@ func (c *Crypto) OnPersist(ic *interop.Context) error { func (c *Crypto) PostPersist(ic *interop.Context) error { return nil } + +// ActiveIn implements the Contract interface. +func (c *Crypto) ActiveIn() *config.Hardfork { + return nil +} diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 0e295d593..b8d5cd016 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -9,6 +9,7 @@ import ( "sort" "sync/atomic" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" @@ -194,6 +195,11 @@ func (s *Designate) Metadata() *interop.ContractMD { return &s.ContractMD } +// ActiveIn implements the Contract interface. +func (s *Designate) ActiveIn() *config.Hardfork { + return nil +} + func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.Item) stackitem.Item { r, ok := s.getRole(args[0]) if !ok { diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 7f250b4a7..5d2d1c230 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -18,24 +18,30 @@ func Call(ic *interop.Context) error { if version != 0 { return fmt.Errorf("native contract of version %d is not active", version) } - var meta *interop.ContractMD - curr := ic.VM.GetCurrentScriptHash() + var ( + c interop.Contract + curr = ic.VM.GetCurrentScriptHash() + ) for _, ctr := range ic.Natives { - m := ctr.Metadata() - if m.Hash == curr { - meta = m + if ctr.Metadata().Hash == curr { + c = ctr break } } - if meta == nil { + if c == nil { return fmt.Errorf("native contract %s (version %d) not found", curr.StringLE(), version) } - history := meta.UpdateHistory - if len(history) == 0 { - return fmt.Errorf("native contract %s is disabled", meta.Name) - } - if history[0] > ic.BlockHeight() { // persisting block must not be taken into account. - return fmt.Errorf("native contract %s is active after height = %d", meta.Name, history[0]) + var ( + meta = c.Metadata() + activeIn = c.ActiveIn() + ) + if activeIn != nil { + height, ok := ic.Hardforks[activeIn.String()] + // Persisting block must not be taken into account, native contract can be called + // only AFTER its initialization block persist, thus, can't use ic.IsHardforkEnabled. + if !ok || ic.BlockHeight() < height { + return fmt.Errorf("native contract %s is active after hardfork %s", meta.Name, activeIn.String()) + } } m, ok := meta.GetMethodByOffset(ic.VM.Context().IP()) if !ok { @@ -76,7 +82,8 @@ func OnPersist(ic *interop.Context) error { return errors.New("onPersist must be trigered by system") } for _, c := range ic.Natives { - if !c.Metadata().IsActive(ic.Block.Index) { + activeIn := c.ActiveIn() + if !(activeIn == nil || ic.IsHardforkEnabled(*activeIn)) { continue } err := c.OnPersist(ic) @@ -93,7 +100,8 @@ func PostPersist(ic *interop.Context) error { return errors.New("postPersist must be trigered by system") } for _, c := range ic.Natives { - if !c.Metadata().IsActive(ic.Block.Index) { + activeIn := c.ActiveIn() + if !(activeIn == nil || ic.IsHardforkEnabled(*activeIn)) { continue } err := c.PostPersist(ic) diff --git a/pkg/core/native/invocation_test.go b/pkg/core/native/invocation_test.go index cc5c117bb..ced088b20 100644 --- a/pkg/core/native/invocation_test.go +++ b/pkg/core/native/invocation_test.go @@ -89,48 +89,50 @@ func TestNativeContract_InvokeInternal(t *testing.T) { require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error()) }) - t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) { - bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) { - c.NativeUpdateHistories = map[string][]uint32{ - nativenames.Policy: {0}, - nativenames.Neo: {0}, - nativenames.Gas: {0}, - nativenames.Designation: {0}, - nativenames.StdLib: {0}, - nativenames.Management: {0}, - nativenames.Oracle: {0}, - nativenames.Ledger: {0}, - nativenames.CryptoLib: {1}, - } + /* + t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) { + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) { + c.NativeUpdateHistories = map[string][]uint32{ + nativenames.Policy: {0}, + nativenames.Neo: {0}, + nativenames.Gas: {0}, + nativenames.Designation: {0}, + nativenames.StdLib: {0}, + nativenames.Management: {0}, + nativenames.Oracle: {0}, + nativenames.Ledger: {0}, + nativenames.CryptoLib: {1}, + } + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + + ic, err := bcBad.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) + v := ic.SpawnVM() + v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history + input := []byte{1, 2, 3, 4} + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) + + // It's prohibited to call natives before NativeUpdateHistory[0] height. + err = v.Run() + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1")) + + // Add new block => CryptoLib should be active now. + eBad.AddNewBlock(t) + ic, err = bcBad.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) + v = ic.SpawnVM() + v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) + + require.NoError(t, v.Run()) + value := v.Estack().Pop().Bytes() + require.Equal(t, hash.RipeMD160(input).BytesBE(), value) }) - eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) - - ic, err := bcBad.GetTestVM(trigger.Application, nil, nil) - require.NoError(t, err) - v := ic.SpawnVM() - v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history - input := []byte{1, 2, 3, 4} - v.Estack().PushVal(input) - v.Context().Jump(md.Offset) - - // It's prohibited to call natives before NativeUpdateHistory[0] height. - err = v.Run() - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1")) - - // Add new block => CryptoLib should be active now. - eBad.AddNewBlock(t) - ic, err = bcBad.GetTestVM(trigger.Application, nil, nil) - require.NoError(t, err) - v = ic.SpawnVM() - v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history - v.Estack().PushVal(input) - v.Context().Jump(md.Offset) - - require.NoError(t, v.Run()) - value := v.Estack().Pop().Bytes() - require.Equal(t, hash.RipeMD160(input).BytesBE(), value) - }) + */ manState := bc.GetContractState(e.NativeHash(t, nativenames.Management)) require.NotNil(t, manState) diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 6a1d43a22..7fb026679 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -4,6 +4,7 @@ import ( "fmt" "math" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -103,6 +104,11 @@ func (l *Ledger) PostPersist(ic *interop.Context) error { return nil // Actual block/tx processing is done in Blockchain.storeBlock(). } +// ActiveIn implements the Contract interface. +func (l *Ledger) ActiveIn() *config.Hardfork { + return nil +} + // currentHash implements currentHash SC method. func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item { return stackitem.Make(ic.CurrentBlockHash().BytesBE()) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 0841bcc1d..e48609c1c 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -583,12 +583,13 @@ func updateContractCache(cache *ManagementCache, cs *state.Contract) { func (m *Management) OnPersist(ic *interop.Context) error { var cache *ManagementCache for _, native := range ic.Natives { - md := native.Metadata() - history := md.UpdateHistory - if len(history) == 0 || history[0] != ic.Block.Index { + activeIn := native.ActiveIn() + if !(activeIn == nil && ic.Block.Index == 0 || + activeIn != nil && ic.IsHardforkActivation(*activeIn)) { continue } + md := native.Metadata() cs := &state.Contract{ ContractBase: md.ContractBase, } @@ -672,6 +673,11 @@ func (m *Management) Initialize(ic *interop.Context) error { return nil } +// ActiveIn implements the Contract interface. +func (m *Management) ActiveIn() *config.Hardfork { + return nil +} + // PutContractState saves given contract state into given DAO. func PutContractState(d *dao.Simple, cs *state.Contract) error { return putContractState(d, cs, true) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 7276572b6..6fa1eb4fc 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -4,6 +4,7 @@ import ( "errors" "math/big" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -137,6 +138,11 @@ func (g *GAS) PostPersist(ic *interop.Context) error { return nil } +// ActiveIn implements the Contract interface. +func (g *GAS) ActiveIn() *config.Hardfork { + return nil +} + // BalanceOf returns native GAS token balance for the acc. func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int { return g.balanceOfInternal(d, acc) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index f9eb2b640..67b2f7d57 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -365,6 +365,11 @@ func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error { return nil } +// ActiveIn implements the Contract interface. +func (n *NEO) ActiveIn() *config.Hardfork { + return nil +} + func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error { var err error diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index c0b2da285..7f0bb49e7 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -6,6 +6,7 @@ import ( "math" "math/big" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" @@ -210,6 +211,11 @@ func (n *Notary) PostPersist(ic *interop.Context) error { return nil } +// ActiveIn implements the Contract interface. +func (n *Notary) ActiveIn() *config.Hardfork { + return nil +} + // onPayment records the deposited amount as belonging to "from" address with a lock // till the specified chain's height. func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index ef943966e..d07447eaf 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -9,6 +9,7 @@ import ( "strings" "sync/atomic" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" @@ -258,6 +259,11 @@ func (o *Oracle) InitializeCache(blockHeight uint32, d *dao.Simple) error { return nil } +// ActiveIn implements the Contract interface. +func (o *Oracle) ActiveIn() *config.Hardfork { + return nil +} + func getResponse(tx *transaction.Transaction) *transaction.OracleResponse { for i := range tx.Attributes { if tx.Attributes[i].Type == transaction.OracleResponseT { diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index d3fb5c001..5a0a57582 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -5,6 +5,7 @@ import ( "math/big" "sort" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -196,6 +197,11 @@ func (p *Policy) PostPersist(ic *interop.Context) error { return nil } +// ActiveIn implements the Contract interface. +func (p *Policy) ActiveIn() *config.Hardfork { + return nil +} + // getFeePerByte is a Policy contract method that returns the required transaction's fee // per byte. func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item { diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go index 86f013cf1..32c17fa62 100644 --- a/pkg/core/native/std.go +++ b/pkg/core/native/std.go @@ -458,6 +458,11 @@ func (s *Std) PostPersist(ic *interop.Context) error { return nil } +// ActiveIn implements the Contract interface. +func (s *Std) ActiveIn() *config.Hardfork { + return nil +} + func (s *Std) toLimitedBytes(item stackitem.Item) []byte { src, err := item.TryBytes() if err != nil { diff --git a/pkg/services/rpcsrv/server_test.go b/pkg/services/rpcsrv/server_test.go index f559258a4..447ba839f 100644 --- a/pkg/services/rpcsrv/server_test.go +++ b/pkg/services/rpcsrv/server_test.go @@ -1194,7 +1194,6 @@ var rpcTestCases = map[string][]rpcTestCase{ cs := e.chain.GetContractState((*lst)[i].Hash) require.NotNil(t, cs) require.True(t, cs.ID <= 0) - require.Equal(t, []uint32{0}, (*lst)[i].UpdateHistory) } }, },