Merge pull request #3212 from nspcc-dev/drop-history

Move NativeUpdateHistory logic uner hardforks
This commit is contained in:
Roman Khimov 2023-11-21 15:46:16 +03:00 committed by GitHub
commit ff958706f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 170 additions and 306 deletions

View file

@ -26,17 +26,6 @@ ProtocolConfiguration:
Hardforks: Hardforks:
Aspidochelone: 3000000 Aspidochelone: 3000000
Basilisk: 4500000 Basilisk: 4500000
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
Notary: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -38,16 +38,6 @@ ProtocolConfiguration:
Hardforks: Hardforks:
Aspidochelone: 1730000 Aspidochelone: 1730000
Basilisk: 4120000 Basilisk: 4120000
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -16,16 +16,6 @@ ProtocolConfiguration:
- node_four:20336 - node_four:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -16,16 +16,6 @@ ProtocolConfiguration:
- node_four:20336 - node_four:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -10,16 +10,6 @@ ProtocolConfiguration:
- node_single:20333 - node_single:20333
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -16,16 +16,6 @@ ProtocolConfiguration:
- node_four:20336 - node_four:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -16,16 +16,6 @@ ProtocolConfiguration:
- node_four:20336 - node_four:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -16,16 +16,6 @@ ProtocolConfiguration:
- localhost:20336 - localhost:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: false P2PSigExtensions: false
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -24,17 +24,6 @@ ProtocolConfiguration:
- morph7.t5.fs.neo.org:50333 - morph7.t5.fs.neo.org:50333
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: 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: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -41,16 +41,6 @@ ProtocolConfiguration:
Hardforks: Hardforks:
Aspidochelone: 210000 Aspidochelone: 210000
Basilisk: 2680000 Basilisk: 2680000
NativeActivations:
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
ApplicationConfiguration: ApplicationConfiguration:
SkipBlockVerification: false SkipBlockVerification: false

View file

@ -8,17 +8,6 @@ ProtocolConfiguration:
ValidatorsCount: 1 ValidatorsCount: 1
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: 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: Hardforks:
Aspidochelone: 25 Aspidochelone: 25

View file

@ -17,17 +17,6 @@ ProtocolConfiguration:
- 127.0.0.1:20336 - 127.0.0.1:20336
VerifyTransactions: true VerifyTransactions: true
P2PSigExtensions: 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: Hardforks:
Aspidochelone: 25 Aspidochelone: 25

View file

@ -334,7 +334,6 @@ protocol-related settings described in the table below.
| MaxTransactionsPerBlock | `uint16` | `512` | Maximum number of transactions per block. | | 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. | | 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. | | 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]<br>StdLib: [0]<br>CryptoLib: [0]<br>LedgerContract: [0]<br>NeoToken: [0]<br>GasToken: [0]<br>PolicyContract: [0]<br>RoleManagement: [0]<br>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.<br>This option is valid only if `P2PSigExtensions` are enabled. | Not supported by the C# node, thus may affect heterogeneous networks functionality. | | 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.<br>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:<br>• Transaction attribute `NotaryAssisted`<br>• Network payload of the `P2PNotaryRequest` type<br>• Native `Notary` contract<br>• Notary node module | Not supported by the C# node, thus may affect heterogeneous networks functionality. | | P2PSigExtensions | `bool` | `false` | Enables following additional Notary service related logic:<br>• Transaction attribute `NotaryAssisted`<br>• Network payload of the `P2PNotaryRequest` type<br>• Native `Notary` contract<br>• 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: <br>`StateSyncInterval` protocol setting <br>• 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`). | | P2PStateExchangeExtensions | `bool` | `false` | Enables the following P2P MPT state data exchange logic: <br>`StateSyncInterval` protocol setting <br>• 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 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 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 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 - `Transaction` is a container for transaction script that should be deployed in
the genesis block if provided. `Transaction` includes `Script` which is a the genesis block if provided. `Transaction` includes `Script` which is a

View file

@ -171,9 +171,8 @@ verify and broadcast `P2PNotaryRequest` P2P payloads, properly initialize native
Notary contract and designate `P2PNotary` node role in RoleManagement native Notary contract and designate `P2PNotary` node role in RoleManagement native
contract. contract.
If you use custom `NativeActivations` subsection of the `ProtocolConfiguration` Currently, Notary contract activation height is not configurable and is always
section in your node config, specify the height of the Notary contract set to 0 (if `P2PSigExtensions` are enabled).
activation, e.g. `0`.
Note, that even if `P2PSigExtensions` config subsection enables notary-related 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. 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 P2PSigExtensions: true
NativeActivations:
Notary: [0]
ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0]
``` ```

View file

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "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" "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 // exceeding that a transaction should fail validation. It is set to estimated daily number
// of blocks with 15s interval. // of blocks with 15s interval.
MaxValidUntilBlockIncrement uint32 `yaml:"MaxValidUntilBlockIncrement"` 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 enables additional signature-related logic.
P2PSigExtensions bool `yaml:"P2PSigExtensions"` P2PSigExtensions bool `yaml:"P2PSigExtensions"`
// P2PStateExchangeExtensions enables additional P2P MPT state data exchange logic. // P2PStateExchangeExtensions enables additional P2P MPT state data exchange logic.
@ -86,11 +83,6 @@ func (p *ProtocolConfiguration) Validate() error {
if p.TimePerBlock%time.Millisecond != 0 { if p.TimePerBlock%time.Millisecond != 0 {
return errors.New("TimePerBlock must be an integer number of milliseconds") 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 { for name := range p.Hardforks {
if !IsHardforkValid(name) { if !IsHardforkValid(name) {
return fmt.Errorf("Hardforks configuration section contains unexpected hardfork: %s", 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 || p.VerifyTransactions != o.VerifyTransactions ||
len(p.CommitteeHistory) != len(o.CommitteeHistory) || len(p.CommitteeHistory) != len(o.CommitteeHistory) ||
len(p.Hardforks) != len(o.Hardforks) || len(p.Hardforks) != len(o.Hardforks) ||
len(p.NativeUpdateHistories) != len(o.NativeUpdateHistories) ||
len(p.SeedList) != len(o.SeedList) || len(p.SeedList) != len(o.SeedList) ||
len(p.StandbyCommittee) != len(o.StandbyCommittee) || len(p.StandbyCommittee) != len(o.StandbyCommittee) ||
len(p.ValidatorsHistory) != len(o.ValidatorsHistory) { len(p.ValidatorsHistory) != len(o.ValidatorsHistory) {
@ -255,17 +246,6 @@ func (p *ProtocolConfiguration) Equals(o *ProtocolConfiguration) bool {
return false 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 { for i := range p.SeedList {
if p.SeedList[i] != o.SeedList[i] { if p.SeedList[i] != o.SeedList[i] {
return false return false

View file

@ -28,12 +28,6 @@ func TestProtocolConfigurationValidation(t *testing.T) {
ValidatorsCount: 1, ValidatorsCount: 1,
} }
require.Error(t, p.Validate()) require.Error(t, p.Validate())
p = &ProtocolConfiguration{
NativeUpdateHistories: map[string][]uint32{
"someContract": {0, 10},
},
}
require.Error(t, p.Validate())
p = &ProtocolConfiguration{ p = &ProtocolConfiguration{
StandbyCommittee: []string{ StandbyCommittee: []string{
"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2", "02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2",
@ -247,17 +241,6 @@ func TestProtocolConfigurationEquals(t *testing.T) {
p.Hardforks = nil p.Hardforks = nil
o.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"} p.SeedList = []string{"url1", "url2"}
o.SeedList = []string{"url1", "url2"} o.SeedList = []string{"url1", "url2"}
require.True(t, p.Equals(o)) require.True(t, p.Equals(o))

View file

@ -1,4 +0,0 @@
ProtocolConfiguration:
NativeActivations:
ContractManagement: [0]
UnexpectedContractName: [0]

View file

@ -283,10 +283,6 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger) (*Bl
zap.Int("StateSyncInterval", cfg.StateSyncInterval)) 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 { if cfg.Hardforks == nil {
cfg.Hardforks = map[string]uint32{} cfg.Hardforks = map[string]uint32{}
for _, hf := range config.Hardforks { for _, hf := range config.Hardforks {
@ -485,8 +481,8 @@ func (bc *Blockchain) init() error {
for _, c := range bc.contracts.Contracts { for _, c := range bc.contracts.Contracts {
md := c.Metadata() md := c.Metadata()
storedCS := bc.GetContractState(md.Hash) storedCS := bc.GetContractState(md.Hash)
history := md.UpdateHistory // Check that contract was deployed.
if len(history) == 0 || history[0] > bHeight { if !bc.isHardforkEnabled(c.ActiveIn(), bHeight) {
if storedCS != nil { if storedCS != nil {
return fmt.Errorf("native contract %s is already stored, but marked as inactive for height %d in config", md.Name, bHeight) 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 { func (bc *Blockchain) initializeNativeCache(blockHeight uint32, d *dao.Simple) error {
for _, c := range bc.contracts.Contracts { for _, c := range bc.contracts.Contracts {
for _, h := range c.Metadata().UpdateHistory { // Check that contract was deployed.
if blockHeight >= h { // check that contract was deployed. if !bc.isHardforkEnabled(c.ActiveIn(), blockHeight) {
err := c.InitializeCache(blockHeight, d) continue
if err != nil { }
return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err) err := c.InitializeCache(blockHeight, d)
} if err != nil {
break return fmt.Errorf("failed to initialize cache for %s: %w", c.Metadata().Name, err)
}
} }
} }
return nil 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 // Run runs chain loop, it needs to be run as goroutine and executing it is
// critical for correct Blockchain operation. // critical for correct Blockchain operation.
func (bc *Blockchain) Run() { func (bc *Blockchain) Run() {
@ -3008,8 +3016,8 @@ func (bc *Blockchain) GetMaxNotValidBeforeDelta() (uint32, error) {
if !bc.config.P2PSigExtensions { if !bc.config.P2PSigExtensions {
panic("disallowed call to Notary") // critical error, thus panic. panic("disallowed call to Notary") // critical error, thus panic.
} }
if bc.contracts.Notary.Metadata().UpdateHistory[0] > bc.BlockHeight() { if !bc.isHardforkEnabled(bc.contracts.Notary.ActiveIn(), bc.BlockHeight()) {
return 0, fmt.Errorf("native Notary is active starting from %d", bc.contracts.Notary.Metadata().UpdateHistory[0]) return 0, fmt.Errorf("native Notary is active starting from %s", bc.contracts.Notary.ActiveIn().String())
} }
return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao), nil return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao), nil
} }

View file

@ -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) 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) { t.Run("invalid native contract activation", func(t *testing.T) {
ps = newPS(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 // 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. // Notary cache initialization is performed before that height on node restart.
/*
func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) { func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) {
const notaryEnabledHeight = 3 const notaryEnabledHeight = 3
ps, path := newLevelDBForTestingWithPath(t, "") ps, path := newLevelDBForTestingWithPath(t, "")
@ -399,6 +380,7 @@ func TestBlockchain_InitializeNativeCacheWrtNativeActivations(t *testing.T) {
_, err = e.Chain.GetMaxNotValidBeforeDelta() _, err = e.Chain.GetMaxNotValidBeforeDelta()
require.NoError(t, err) require.NoError(t, err)
} }
*/
func TestBlockchain_AddHeaders(t *testing.T) { func TestBlockchain_AddHeaders(t *testing.T) {
bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.Blockchain) { 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") cValidatorInvoker.Invoke(t, stackitem.Null{}, "delValue", "non-existent-key")
} }
// Test that UpdateHistory is added to ProtocolConfiguration for all native contracts // Test that all default configurations are loadable.
// for all default configurations. If UpdateHistory is not added to config, then func TestConfig_LoadDefaultConfigs(t *testing.T) {
// native contract is disabled. It's easy to forget about config while adding new
// native contract.
func TestConfigNativeUpdateHistory(t *testing.T) {
var prefixPath = filepath.Join("..", "..", "config") var prefixPath = filepath.Join("..", "..", "config")
check := func(t *testing.T, cfgFileSuffix any) { check := func(t *testing.T, cfgFileSuffix any) {
cfgPath := filepath.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix)) 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)) 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{ testCases := []any{
netmode.MainNet, netmode.MainNet,

View file

@ -153,6 +153,9 @@ type MethodAndPrice struct {
// Contract is an interface for all native contracts. // Contract is an interface for all native contracts.
type Contract interface { type Contract interface {
Initialize(*Context) error 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 // 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. // 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 // 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. // Sort sorts interop functions by id.
func Sort(fs []Function) { func Sort(fs []Function) {
sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID }) 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 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. // AddNotification creates notification event and appends it to the notification list.
func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) { func (ic *Context) AddNotification(hash util.Uint160, name string, item *stackitem.Array) {
ic.Notifications = append(ic.Notifications, state.NotificationEvent{ ic.Notifications = append(ic.Notifications, state.NotificationEvent{

View file

@ -110,14 +110,6 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
cs.Contracts = append(cs.Contracts, notary) 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 return cs
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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 { func (c *Crypto) PostPersist(ic *interop.Context) error {
return nil return nil
} }
// ActiveIn implements the Contract interface.
func (c *Crypto) ActiveIn() *config.Hardfork {
return nil
}

View file

@ -9,6 +9,7 @@ import (
"sort" "sort"
"sync/atomic" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
@ -194,6 +195,11 @@ func (s *Designate) Metadata() *interop.ContractMD {
return &s.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 { func (s *Designate) getDesignatedByRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
r, ok := s.getRole(args[0]) r, ok := s.getRole(args[0])
if !ok { if !ok {

View file

@ -18,24 +18,30 @@ func Call(ic *interop.Context) error {
if version != 0 { if version != 0 {
return fmt.Errorf("native contract of version %d is not active", version) return fmt.Errorf("native contract of version %d is not active", version)
} }
var meta *interop.ContractMD var (
curr := ic.VM.GetCurrentScriptHash() c interop.Contract
curr = ic.VM.GetCurrentScriptHash()
)
for _, ctr := range ic.Natives { for _, ctr := range ic.Natives {
m := ctr.Metadata() if ctr.Metadata().Hash == curr {
if m.Hash == curr { c = ctr
meta = m
break break
} }
} }
if meta == nil { if c == nil {
return fmt.Errorf("native contract %s (version %d) not found", curr.StringLE(), version) return fmt.Errorf("native contract %s (version %d) not found", curr.StringLE(), version)
} }
history := meta.UpdateHistory var (
if len(history) == 0 { meta = c.Metadata()
return fmt.Errorf("native contract %s is disabled", meta.Name) activeIn = c.ActiveIn()
} )
if history[0] > ic.BlockHeight() { // persisting block must not be taken into account. if activeIn != nil {
return fmt.Errorf("native contract %s is active after height = %d", meta.Name, history[0]) 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()) m, ok := meta.GetMethodByOffset(ic.VM.Context().IP())
if !ok { if !ok {
@ -76,7 +82,8 @@ func OnPersist(ic *interop.Context) error {
return errors.New("onPersist must be trigered by system") return errors.New("onPersist must be trigered by system")
} }
for _, c := range ic.Natives { for _, c := range ic.Natives {
if !c.Metadata().IsActive(ic.Block.Index) { activeIn := c.ActiveIn()
if !(activeIn == nil || ic.IsHardforkEnabled(*activeIn)) {
continue continue
} }
err := c.OnPersist(ic) err := c.OnPersist(ic)
@ -93,7 +100,8 @@ func PostPersist(ic *interop.Context) error {
return errors.New("postPersist must be trigered by system") return errors.New("postPersist must be trigered by system")
} }
for _, c := range ic.Natives { for _, c := range ic.Natives {
if !c.Metadata().IsActive(ic.Block.Index) { activeIn := c.ActiveIn()
if !(activeIn == nil || ic.IsHardforkEnabled(*activeIn)) {
continue continue
} }
err := c.PostPersist(ic) err := c.PostPersist(ic)

View file

@ -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()) 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) { t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) {
c.NativeUpdateHistories = map[string][]uint32{ bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
nativenames.Policy: {0}, c.NativeUpdateHistories = map[string][]uint32{
nativenames.Neo: {0}, nativenames.Policy: {0},
nativenames.Gas: {0}, nativenames.Neo: {0},
nativenames.Designation: {0}, nativenames.Gas: {0},
nativenames.StdLib: {0}, nativenames.Designation: {0},
nativenames.Management: {0}, nativenames.StdLib: {0},
nativenames.Oracle: {0}, nativenames.Management: {0},
nativenames.Ledger: {0}, nativenames.Oracle: {0},
nativenames.CryptoLib: {1}, 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)) manState := bc.GetContractState(e.NativeHash(t, nativenames.Management))
require.NotNil(t, manState) require.NotNil(t, manState)

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"math" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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(). 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. // currentHash implements currentHash SC method.
func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item { func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.Make(ic.CurrentBlockHash().BytesBE()) return stackitem.Make(ic.CurrentBlockHash().BytesBE())

View file

@ -583,12 +583,13 @@ func updateContractCache(cache *ManagementCache, cs *state.Contract) {
func (m *Management) OnPersist(ic *interop.Context) error { func (m *Management) OnPersist(ic *interop.Context) error {
var cache *ManagementCache var cache *ManagementCache
for _, native := range ic.Natives { for _, native := range ic.Natives {
md := native.Metadata() activeIn := native.ActiveIn()
history := md.UpdateHistory if !(activeIn == nil && ic.Block.Index == 0 ||
if len(history) == 0 || history[0] != ic.Block.Index { activeIn != nil && ic.IsHardforkActivation(*activeIn)) {
continue continue
} }
md := native.Metadata()
cs := &state.Contract{ cs := &state.Contract{
ContractBase: md.ContractBase, ContractBase: md.ContractBase,
} }
@ -672,6 +673,11 @@ func (m *Management) Initialize(ic *interop.Context) error {
return nil return nil
} }
// ActiveIn implements the Contract interface.
func (m *Management) ActiveIn() *config.Hardfork {
return nil
}
// PutContractState saves given contract state into given DAO. // PutContractState saves given contract state into given DAO.
func PutContractState(d *dao.Simple, cs *state.Contract) error { func PutContractState(d *dao.Simple, cs *state.Contract) error {
return putContractState(d, cs, true) return putContractState(d, cs, true)

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"math/big" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
@ -136,6 +137,11 @@ func (g *GAS) PostPersist(ic *interop.Context) error {
return nil return nil
} }
// ActiveIn implements the Contract interface.
func (g *GAS) ActiveIn() *config.Hardfork {
return nil
}
// BalanceOf returns native GAS token balance for the acc. // BalanceOf returns native GAS token balance for the acc.
func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int { func (g *GAS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
return g.balanceOfInternal(d, acc) return g.balanceOfInternal(d, acc)

View file

@ -365,6 +365,11 @@ func (n *NEO) InitializeCache(blockHeight uint32, d *dao.Simple) error {
return nil return nil
} }
// ActiveIn implements the Contract interface.
func (n *NEO) ActiveIn() *config.Hardfork {
return nil
}
func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error { func (n *NEO) initConfigCache(cfg config.ProtocolConfiguration) error {
var err error var err error

View file

@ -6,6 +6,7 @@ import (
"math" "math"
"math/big" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
@ -193,6 +194,11 @@ func (n *Notary) PostPersist(ic *interop.Context) error {
return nil 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 // onPayment records the deposited amount as belonging to "from" address with a lock
// till the specified chain's height. // till the specified chain's height.
func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *Notary) onPayment(ic *interop.Context, args []stackitem.Item) stackitem.Item {

View file

@ -9,6 +9,7 @@ import (
"strings" "strings"
"sync/atomic" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "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 return nil
} }
// ActiveIn implements the Contract interface.
func (o *Oracle) ActiveIn() *config.Hardfork {
return nil
}
func getResponse(tx *transaction.Transaction) *transaction.OracleResponse { func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
for i := range tx.Attributes { for i := range tx.Attributes {
if tx.Attributes[i].Type == transaction.OracleResponseT { if tx.Attributes[i].Type == transaction.OracleResponseT {

View file

@ -6,6 +6,7 @@ import (
"math/big" "math/big"
"sort" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
@ -252,6 +253,11 @@ func (p *Policy) PostPersist(ic *interop.Context) error {
return nil 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 // getFeePerByte is a Policy contract method that returns the required transaction's fee
// per byte. // per byte.
func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item { func (p *Policy) getFeePerByte(ic *interop.Context, _ []stackitem.Item) stackitem.Item {

View file

@ -458,6 +458,11 @@ func (s *Std) PostPersist(ic *interop.Context) error {
return nil return nil
} }
// ActiveIn implements the Contract interface.
func (s *Std) ActiveIn() *config.Hardfork {
return nil
}
func (s *Std) toLimitedBytes(item stackitem.Item) []byte { func (s *Std) toLimitedBytes(item stackitem.Item) []byte {
src, err := item.TryBytes() src, err := item.TryBytes()
if err != nil { if err != nil {

View file

@ -1194,7 +1194,6 @@ var rpcTestCases = map[string][]rpcTestCase{
cs := e.chain.GetContractState((*lst)[i].Hash) cs := e.chain.GetContractState((*lst)[i].Hash)
require.NotNil(t, cs) require.NotNil(t, cs)
require.True(t, cs.ID <= 0) require.True(t, cs.ID <= 0)
require.Equal(t, []uint32{0}, (*lst)[i].UpdateHistory)
} }
}, },
}, },