*: move NativeUpdateHistory logic under Hardforks management

Close #3196.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-11-21 12:35:18 +03:00
parent 4d8601297d
commit 58102a9a80
34 changed files with 170 additions and 306 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]<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. |
| 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`). |
@ -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

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
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]
```

View file

@ -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

View file

@ -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))

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))
}
}
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
}

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)
})
*/
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,

View file

@ -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{

View file

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

View file

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

View file

@ -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 {

View file

@ -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)

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())
})
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)

View file

@ -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())

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}
},
},