forked from TrueCloudLab/neoneo-go
Merge pull request #1827 from nspcc-dev/native/update_history
config: add NativeUpdateHistory
This commit is contained in:
commit
a18fbc7bb1
27 changed files with 270 additions and 11 deletions
|
@ -21,6 +21,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
P2PSigExtensions: false
|
P2PSigExtensions: false
|
||||||
|
NativeActivations:
|
||||||
|
ContractManagement: [0]
|
||||||
|
StdLib: [0]
|
||||||
|
CryptoLib: [0]
|
||||||
|
LedgerContract: [0]
|
||||||
|
NeoToken: [0]
|
||||||
|
GasToken: [0]
|
||||||
|
PolicyContract: [0]
|
||||||
|
RoleManagement: [0]
|
||||||
|
OracleContract: [0]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -17,6 +17,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -17,6 +17,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -11,6 +11,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -17,6 +17,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -17,6 +17,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -17,6 +17,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -21,6 +21,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
VerifyTransactions: false
|
VerifyTransactions: false
|
||||||
P2PSigExtensions: false
|
P2PSigExtensions: false
|
||||||
|
NativeActivations:
|
||||||
|
ContractManagement: [0]
|
||||||
|
StdLib: [0]
|
||||||
|
CryptoLib: [0]
|
||||||
|
LedgerContract: [0]
|
||||||
|
NeoToken: [0]
|
||||||
|
GasToken: [0]
|
||||||
|
PolicyContract: [0]
|
||||||
|
RoleManagement: [0]
|
||||||
|
OracleContract: [0]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -9,6 +9,17 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -18,6 +18,18 @@ ProtocolConfiguration:
|
||||||
VerifyBlocks: true
|
VerifyBlocks: true
|
||||||
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]
|
||||||
|
NameService: [0]
|
||||||
|
Notary: [0]
|
||||||
|
|
||||||
ApplicationConfiguration:
|
ApplicationConfiguration:
|
||||||
# LogPath could be set up in case you need stdout logs to some proper file.
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContractHashes(t *testing.T) {
|
func TestContractHashes(t *testing.T) {
|
||||||
cs := native.NewContracts(true)
|
cs := native.NewContracts(true, map[string][]uint32{})
|
||||||
require.Equal(t, []byte(neo.Hash), cs.NEO.Hash.BytesBE())
|
require.Equal(t, []byte(neo.Hash), cs.NEO.Hash.BytesBE())
|
||||||
require.Equal(t, []byte(gas.Hash), cs.GAS.Hash.BytesBE())
|
require.Equal(t, []byte(gas.Hash), cs.GAS.Hash.BytesBE())
|
||||||
require.Equal(t, []byte(oracle.Hash), cs.Oracle.Hash.BytesBE())
|
require.Equal(t, []byte(oracle.Hash), cs.Oracle.Hash.BytesBE())
|
||||||
|
@ -93,7 +93,7 @@ type nativeTestCase struct {
|
||||||
|
|
||||||
// Here we test that corresponding method does exist, is invoked and correct value is returned.
|
// Here we test that corresponding method does exist, is invoked and correct value is returned.
|
||||||
func TestNativeHelpersCompile(t *testing.T) {
|
func TestNativeHelpersCompile(t *testing.T) {
|
||||||
cs := native.NewContracts(true)
|
cs := native.NewContracts(true, map[string][]uint32{})
|
||||||
u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")`
|
u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")`
|
||||||
u256 := `interop.Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")`
|
u256 := `interop.Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")`
|
||||||
pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")`
|
pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")`
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,5 +57,11 @@ func LoadFile(configPath string) (Config, error) {
|
||||||
return Config{}, fmt.Errorf("failed to unmarshal config YAML: %w", err)
|
return Config{}, fmt.Errorf("failed to unmarshal config YAML: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name := range config.ProtocolConfiguration.NativeUpdateHistories {
|
||||||
|
if !nativenames.IsValid(name) {
|
||||||
|
return Config{}, fmt.Errorf("NativeActivations configuration section contains unexpected native contract name: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
14
pkg/config/config_test.go
Normal file
14
pkg/config/config_test.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testConfigPath = "./testdata/protocol.test.yml"
|
||||||
|
|
||||||
|
func TestUnexpectedNativeUpdateHistoryContract(t *testing.T) {
|
||||||
|
_, err := LoadFile(testConfigPath)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ type (
|
||||||
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
MaxTraceableBlocks uint32 `yaml:"MaxTraceableBlocks"`
|
||||||
// MaxTransactionsPerBlock is the maximum amount of transactions per block.
|
// MaxTransactionsPerBlock is the maximum amount of transactions per block.
|
||||||
MaxTransactionsPerBlock uint16 `yaml:"MaxTransactionsPerBlock"`
|
MaxTransactionsPerBlock uint16 `yaml:"MaxTransactionsPerBlock"`
|
||||||
|
// NativeUpdateHistories is the 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"`
|
||||||
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
// ReservedAttributes allows to have reserved attributes range for experimental or private purposes.
|
||||||
|
|
4
pkg/config/testdata/protocol.test.yml
vendored
Normal file
4
pkg/config/testdata/protocol.test.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
ProtocolConfiguration:
|
||||||
|
NativeActivations:
|
||||||
|
ContractManagement: [0]
|
||||||
|
UnexpectedContractName: [0]
|
|
@ -180,6 +180,10 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(cfg.NativeUpdateHistories) == 0 {
|
||||||
|
cfg.NativeUpdateHistories = map[string][]uint32{}
|
||||||
|
log.Info("NativeActivations are not set, using default values")
|
||||||
|
}
|
||||||
bc := &Blockchain{
|
bc := &Blockchain{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
dao: dao.NewSimple(s, cfg.Magic, cfg.StateRootInHeader),
|
||||||
|
@ -192,7 +196,7 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
subCh: make(chan interface{}),
|
subCh: make(chan interface{}),
|
||||||
unsubCh: make(chan interface{}),
|
unsubCh: make(chan interface{}),
|
||||||
|
|
||||||
contracts: *native.NewContracts(cfg.P2PSigExtensions),
|
contracts: *native.NewContracts(cfg.P2PSigExtensions, cfg.NativeUpdateHistories),
|
||||||
}
|
}
|
||||||
|
|
||||||
bc.stateRoot = stateroot.NewModule(bc, bc.log, bc.dao.Store)
|
bc.stateRoot = stateroot.NewModule(bc, bc.log, bc.dao.Store)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1614,3 +1615,40 @@ func TestMPTDeleteNoKey(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
require.Equal(t, vm.HaltState, aer.VMState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
const prefixPath = "../../config"
|
||||||
|
check := func(t *testing.T, cfgFileSuffix interface{}) {
|
||||||
|
cfgPath := path.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix))
|
||||||
|
cfg, err := config.LoadFile(cfgPath)
|
||||||
|
require.NoError(t, err, fmt.Errorf("failed to load %s", cfgPath))
|
||||||
|
natives := native.NewContracts(cfg.ProtocolConfiguration.P2PSigExtensions, map[string][]uint32{})
|
||||||
|
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 := []interface{}{
|
||||||
|
netmode.MainNet,
|
||||||
|
netmode.PrivNet,
|
||||||
|
netmode.TestNet,
|
||||||
|
netmode.UnitTestNet,
|
||||||
|
"privnet.docker.one",
|
||||||
|
"privnet.docker.two",
|
||||||
|
"privnet.docker.three",
|
||||||
|
"privnet.docker.four",
|
||||||
|
"privnet.docker.single",
|
||||||
|
"unit_testnet.single",
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
check(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -209,6 +209,12 @@ func (c *ContractMD) AddEvent(name string, ps ...manifest.Parameter) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsActive returns true iff 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 })
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
// "C" and "O" can easily be typed by accident.
|
// "C" and "O" can easily be typed by accident.
|
||||||
func TestNamesASCII(t *testing.T) {
|
func TestNamesASCII(t *testing.T) {
|
||||||
cs := NewContracts(true)
|
cs := NewContracts(true, map[string][]uint32{})
|
||||||
for _, c := range cs.Contracts {
|
for _, c := range cs.Contracts {
|
||||||
require.True(t, isASCII(c.Metadata().Name))
|
require.True(t, isASCII(c.Metadata().Name))
|
||||||
for _, m := range c.Metadata().Methods {
|
for _, m := range c.Metadata().Methods {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (cs *Contracts) ByName(name string) interop.Contract {
|
||||||
|
|
||||||
// NewContracts returns new set of native contracts with new GAS, NEO, Policy, Oracle,
|
// NewContracts returns new set of native contracts with new GAS, NEO, Policy, Oracle,
|
||||||
// Designate and (optional) Notary contracts.
|
// Designate and (optional) Notary contracts.
|
||||||
func NewContracts(p2pSigExtensionsEnabled bool) *Contracts {
|
func NewContracts(p2pSigExtensionsEnabled bool, nativeUpdateHistories map[string][]uint32) *Contracts {
|
||||||
cs := new(Contracts)
|
cs := new(Contracts)
|
||||||
|
|
||||||
mgmt := newManagement()
|
mgmt := newManagement()
|
||||||
|
@ -117,6 +117,13 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts {
|
||||||
cs.Contracts = append(cs.Contracts, notary)
|
cs.Contracts = append(cs.Contracts, notary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDefaultHistory := len(nativeUpdateHistories) == 0
|
||||||
|
for _, c := range cs.Contracts {
|
||||||
|
if setDefaultHistory {
|
||||||
|
nativeUpdateHistories[c.Metadata().Name] = []uint32{0}
|
||||||
|
}
|
||||||
|
c.Metadata().NativeContract.UpdateHistory = nativeUpdateHistories[c.Metadata().Name]
|
||||||
|
}
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,13 @@ func Call(ic *interop.Context) error {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return fmt.Errorf("native contract %d not found", version)
|
return fmt.Errorf("native contract %d not found", version)
|
||||||
}
|
}
|
||||||
|
history := c.Metadata().UpdateHistory
|
||||||
|
if len(history) == 0 {
|
||||||
|
return fmt.Errorf("native contract %s is disabled", c.Metadata().Name)
|
||||||
|
}
|
||||||
|
if history[0] > ic.Chain.BlockHeight() {
|
||||||
|
return fmt.Errorf("native contract %s is active after height = %d", c.Metadata().Name, history[0])
|
||||||
|
}
|
||||||
m, ok := c.Metadata().GetMethodByOffset(ic.VM.Context().IP())
|
m, ok := c.Metadata().GetMethodByOffset(ic.VM.Context().IP())
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("method not found")
|
return fmt.Errorf("method not found")
|
||||||
|
@ -57,6 +64,9 @@ 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) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
err := c.OnPersist(ic)
|
err := c.OnPersist(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -71,6 +81,9 @@ 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) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
err := c.PostPersist(ic)
|
err := c.PostPersist(ic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -446,12 +446,12 @@ func (m *Management) Metadata() *interop.ContractMD {
|
||||||
|
|
||||||
// OnPersist implements Contract interface.
|
// OnPersist implements Contract interface.
|
||||||
func (m *Management) OnPersist(ic *interop.Context) error {
|
func (m *Management) OnPersist(ic *interop.Context) error {
|
||||||
if ic.Block.Index != 0 { // We're only deploying at 0 at the moment.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, native := range ic.Natives {
|
for _, native := range ic.Natives {
|
||||||
md := native.Metadata()
|
md := native.Metadata()
|
||||||
|
history := md.UpdateHistory
|
||||||
|
if len(history) == 0 || history[0] != ic.Block.Index {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
ContractBase: md.ContractBase,
|
ContractBase: md.ContractBase,
|
||||||
|
|
|
@ -14,3 +14,18 @@ const (
|
||||||
CryptoLib = "CryptoLib"
|
CryptoLib = "CryptoLib"
|
||||||
StdLib = "StdLib"
|
StdLib = "StdLib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsValid checks that name is a valid native contract's name.
|
||||||
|
func IsValid(name string) bool {
|
||||||
|
return name == Management ||
|
||||||
|
name == Ledger ||
|
||||||
|
name == Neo ||
|
||||||
|
name == Gas ||
|
||||||
|
name == Policy ||
|
||||||
|
name == Oracle ||
|
||||||
|
name == Designation ||
|
||||||
|
name == Notary ||
|
||||||
|
name == NameService ||
|
||||||
|
name == CryptoLib ||
|
||||||
|
name == StdLib
|
||||||
|
}
|
||||||
|
|
19
pkg/core/native/nativenames_test.go
Normal file
19
pkg/core/native/nativenames_test.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNativenamesIsValid(t *testing.T) {
|
||||||
|
// test that all native names has been added to IsValid
|
||||||
|
contracts := NewContracts(true, map[string][]uint32{})
|
||||||
|
for _, c := range contracts.Contracts {
|
||||||
|
require.True(t, nativenames.IsValid(c.Metadata().Name), fmt.Errorf("add %s to nativenames.IsValid(...)", c))
|
||||||
|
}
|
||||||
|
|
||||||
|
require.False(t, nativenames.IsValid("unkonwn"))
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ var _ interop.Contract = (*testNative)(nil)
|
||||||
// registerNative registers native contract in the blockchain.
|
// registerNative registers native contract in the blockchain.
|
||||||
func (bc *Blockchain) registerNative(c interop.Contract) {
|
func (bc *Blockchain) registerNative(c interop.Contract) {
|
||||||
bc.contracts.Contracts = append(bc.contracts.Contracts, c)
|
bc.contracts.Contracts = append(bc.contracts.Contracts, c)
|
||||||
|
bc.config.NativeUpdateHistories[c.Metadata().Name] = c.Metadata().UpdateHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -62,8 +63,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestNative() *testNative {
|
func newTestNative() *testNative {
|
||||||
|
cMD := interop.NewContractMD("Test.Native.Sum", 0)
|
||||||
|
cMD.UpdateHistory = []uint32{0}
|
||||||
tn := &testNative{
|
tn := &testNative{
|
||||||
meta: *interop.NewContractMD("Test.Native.Sum", 0),
|
meta: *cMD,
|
||||||
blocks: make(chan uint32, 1),
|
blocks: make(chan uint32, 1),
|
||||||
}
|
}
|
||||||
defer tn.meta.UpdateHash()
|
defer tn.meta.UpdateHash()
|
||||||
|
@ -246,6 +249,21 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
|
||||||
require.Error(t, v.Run())
|
require.Error(t, v.Run())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) {
|
||||||
|
tn.Metadata().UpdateHistory = []uint32{chain.blockHeight + 1}
|
||||||
|
v := ic.SpawnVM()
|
||||||
|
v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All)
|
||||||
|
v.Estack().PushVal(14)
|
||||||
|
v.Estack().PushVal(28)
|
||||||
|
v.Jump(v.Context(), sumOffset)
|
||||||
|
|
||||||
|
// it's prohibited to call natives before NativeUpdateHistory[0] height
|
||||||
|
require.Error(t, v.Run())
|
||||||
|
|
||||||
|
// set the value back to 0
|
||||||
|
tn.Metadata().UpdateHistory = []uint32{0}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
v := ic.SpawnVM()
|
v := ic.SpawnVM()
|
||||||
v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All)
|
v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All)
|
||||||
|
|
|
@ -32,7 +32,7 @@ type ContractBase struct {
|
||||||
// NativeContract holds information about native contract.
|
// NativeContract holds information about native contract.
|
||||||
type NativeContract struct {
|
type NativeContract struct {
|
||||||
ContractBase
|
ContractBase
|
||||||
ActiveBlockIndex uint32 `json:"activeblockindex"`
|
UpdateHistory []uint32 `json:"updatehistory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements Serializable interface.
|
// DecodeBinary implements Serializable interface.
|
||||||
|
|
|
@ -566,6 +566,7 @@ 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)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue