Merge pull request #3168 from nspcc-dev/roles-init
Introduce Genesis protocol setting extensions
This commit is contained in:
commit
890f64007a
23 changed files with 590 additions and 36 deletions
|
@ -325,6 +325,7 @@ protocol-related settings described in the table below.
|
|||
| Section | Type | Default value | Description | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| CommitteeHistory | map[uint32]uint32 | none | Number of committee members after the given height, for example `{0: 1, 20: 4}` sets up a chain with one committee member since the genesis and then changes the setting to 4 committee members at the height of 20. `StandbyCommittee` committee setting must have the number of keys equal or exceeding the highest value in this option. Blocks numbers where the change happens must be divisible by the old and by the new values simultaneously. If not set, committee size is derived from the `StandbyCommittee` setting and never changes. |
|
||||
| Genesis | [Genesis](#Genesis-Configuration) | none | The set of genesis block settings including NeoGo-specific protocol extensions that should be enabled at the genesis block or during native contracts initialisation. |
|
||||
| Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known hard-fork is applied from the zero blockchain height". The list of valid hard-fork names:<br>• `Aspidochelone` represents hard-fork introduced in [#2469](https://github.com/nspcc-dev/neo-go/pull/2469) (ported from the [reference](https://github.com/neo-project/neo/pull/2712)). It adjusts the prices of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. It also includes [#2519](https://github.com/nspcc-dev/neo-go/pull/2519) (ported from the [reference](https://github.com/neo-project/neo/pull/2749)) that adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. A special NeoGo-specific change is included as well for ContractManagement's update/deploy call flags behaviour to be compatible with pre-0.99.0 behaviour that was changed because of the [3.2.0 protocol change](https://github.com/neo-project/neo/pull/2653).<br>• `Basilisk` represents hard-fork introduced in [#3056](https://github.com/nspcc-dev/neo-go/pull/3056) (ported from the [reference](https://github.com/neo-project/neo/pull/2881)). It enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. It also includes [#3080](https://github.com/nspcc-dev/neo-go/pull/3080) (ported from the [reference](https://github.com/neo-project/neo/pull/2883)) that increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. It also includes [#3085](https://github.com/nspcc-dev/neo-go/pull/3085) (ported from the [reference](https://github.com/neo-project/neo/pull/2810)) that enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. |
|
||||
| Magic | `uint32` | `0` | Magic number which uniquely identifies Neo network. |
|
||||
| MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. |
|
||||
|
@ -346,3 +347,54 @@ protocol-related settings described in the table below.
|
|||
| ValidatorsCount | `uint32` | `0` | Number of validators set for the whole network lifetime, can't be set if `ValidatorsHistory` setting is used. |
|
||||
| ValidatorsHistory | map[uint32]uint32 | none | Number of consensus nodes to use after given height (see `CommitteeHistory` also). Heights where the change occurs must be divisible by the number of committee members at that height. Can't be used with `ValidatorsCount` not equal to zero. |
|
||||
| VerifyTransactions | `bool` | `false` | Denotes whether to verify transactions in the received blocks. |
|
||||
|
||||
### Genesis Configuration
|
||||
|
||||
`Genesis` subsection of protocol configuration section contains a set of settings
|
||||
specific for genesis block including NeoGo node extensions that should be enabled
|
||||
during genesis block persist or at the moment of native contracts initialisation.
|
||||
`Genesis` has the following structure:
|
||||
```
|
||||
Genesis:
|
||||
Roles:
|
||||
NeoFSAlphabet:
|
||||
- 033238fa63bd08115ebf442d4af897eea2f6866e4c2001cd1f6e7656acdd91a5d3
|
||||
- 03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c
|
||||
- 02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e
|
||||
- 03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050
|
||||
Oracle:
|
||||
- 03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0
|
||||
- 0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30
|
||||
Transaction:
|
||||
Script: "DCECEDp/fdAWVYWX95YNJ8UWpDlP2Wi55lFV60sBPkBAQG5BVuezJw=="
|
||||
SystemFee: 100000000
|
||||
```
|
||||
where:
|
||||
- `Roles` is a map from node roles that should be set at the moment of native
|
||||
RoleManagement contract initialisation to the list of hex-encoded public keys
|
||||
corresponding to this role. The set of valid roles includes:
|
||||
- `StateValidator`
|
||||
- `Oracle`
|
||||
- `NeoFSAlphabet`
|
||||
- `P2PNotary`
|
||||
|
||||
Roles designation order follows the enumeration above. Designation
|
||||
notifications will be emitted after each configured role designation.
|
||||
|
||||
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
|
||||
only during native RoleManagement contract initialisation (which may be
|
||||
performed in some non-genesis block). 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
|
||||
base64-encoded transaction script and `SystemFee` which is a transaction's
|
||||
system fee value (in GAS) that will be spent during transaction execution.
|
||||
Transaction generated from the provided parameters has two signers at max with
|
||||
CalledByEntry witness scope: the first one is standby validators multisignature
|
||||
signer and the second one (if differs from the first) is committee
|
||||
multisignature signer.
|
||||
|
||||
Note that `Transaction` is a NeoGo extension that isn't supported by the NeoC#
|
||||
node and must be disabled on the public Neo N3 networks.
|
||||
|
|
91
pkg/config/genesis_extensions.go
Normal file
91
pkg/config/genesis_extensions.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
// Genesis represents a set of genesis block settings including the extensions
|
||||
// enabled in the genesis block or during native contracts initialization.
|
||||
type Genesis struct {
|
||||
// Roles contains the set of roles that should be designated during native
|
||||
// Designation contract initialization. It is NeoGo extension and must be
|
||||
// disabled on the public Neo N3 networks.
|
||||
Roles map[noderoles.Role]keys.PublicKeys
|
||||
// Transaction contains transaction script that should be deployed in the
|
||||
// genesis block. It is NeoGo extension and must be disabled on the public
|
||||
// Neo N3 networks.
|
||||
Transaction *GenesisTransaction
|
||||
}
|
||||
|
||||
// GenesisTransaction is a placeholder for script that should be included into genesis
|
||||
// block as a transaction script with the given system fee. Provided
|
||||
// system fee value will be taken from the standby validators account which is
|
||||
// added to the list of Signers as a sender with CalledByEntry scope.
|
||||
type GenesisTransaction struct {
|
||||
Script []byte
|
||||
SystemFee int64
|
||||
}
|
||||
|
||||
type (
|
||||
// genesisAux is an auxiliary structure for Genesis YAML marshalling.
|
||||
genesisAux struct {
|
||||
Roles map[string]keys.PublicKeys `yaml:"Roles"`
|
||||
Transaction *genesisTransactionAux `yaml:"Transaction"`
|
||||
}
|
||||
// genesisTransactionAux is an auxiliary structure for GenesisTransaction YAML
|
||||
// marshalling.
|
||||
genesisTransactionAux struct {
|
||||
Script string `yaml:"Script"`
|
||||
SystemFee int64 `yaml:"SystemFee"`
|
||||
}
|
||||
)
|
||||
|
||||
// MarshalYAML implements the YAML marshaler interface.
|
||||
func (e Genesis) MarshalYAML() (any, error) {
|
||||
var aux genesisAux
|
||||
aux.Roles = make(map[string]keys.PublicKeys, len(e.Roles))
|
||||
for r, ks := range e.Roles {
|
||||
aux.Roles[r.String()] = ks
|
||||
}
|
||||
if e.Transaction != nil {
|
||||
aux.Transaction = &genesisTransactionAux{
|
||||
Script: base64.StdEncoding.EncodeToString(e.Transaction.Script),
|
||||
SystemFee: e.Transaction.SystemFee,
|
||||
}
|
||||
}
|
||||
return aux, nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the YAML unmarshaler interface.
|
||||
func (e *Genesis) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var aux genesisAux
|
||||
if err := unmarshal(&aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Roles = make(map[noderoles.Role]keys.PublicKeys)
|
||||
for s, ks := range aux.Roles {
|
||||
r, ok := noderoles.FromString(s)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown node role: %s", s)
|
||||
}
|
||||
e.Roles[r] = ks
|
||||
}
|
||||
|
||||
if aux.Transaction != nil {
|
||||
script, err := base64.StdEncoding.DecodeString(aux.Transaction.Script)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode script of genesis transaction: %w", err)
|
||||
}
|
||||
e.Transaction = &GenesisTransaction{
|
||||
Script: script,
|
||||
SystemFee: aux.Transaction.SystemFee,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -10,16 +10,26 @@ func _() {
|
|||
var x [1]struct{}
|
||||
_ = x[HFAspidochelone-1]
|
||||
_ = x[HFBasilisk-2]
|
||||
_ = x[hfLast-4]
|
||||
}
|
||||
|
||||
const _Hardfork_name = "AspidocheloneBasilisk"
|
||||
const (
|
||||
_Hardfork_name_0 = "AspidocheloneBasilisk"
|
||||
_Hardfork_name_1 = "hfLast"
|
||||
)
|
||||
|
||||
var _Hardfork_index = [...]uint8{0, 13, 21}
|
||||
var (
|
||||
_Hardfork_index_0 = [...]uint8{0, 13, 21}
|
||||
)
|
||||
|
||||
func (i Hardfork) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 2:
|
||||
i -= 1
|
||||
if i >= Hardfork(len(_Hardfork_index)-1) {
|
||||
return "Hardfork(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
return _Hardfork_name_0[_Hardfork_index_0[i]:_Hardfork_index_0[i+1]]
|
||||
case i == 4:
|
||||
return _Hardfork_name_1
|
||||
default:
|
||||
return "Hardfork(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Hardfork_name[_Hardfork_index[i]:_Hardfork_index[i+1]]
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ type (
|
|||
ProtocolConfiguration struct {
|
||||
// CommitteeHistory stores committee size change history (height: size).
|
||||
CommitteeHistory map[uint32]uint32 `yaml:"CommitteeHistory"`
|
||||
// Genesis stores genesis-related settings including a set of NeoGo
|
||||
// extensions that should be included into genesis block or be enabled
|
||||
// at the moment of native contracts initialization.
|
||||
Genesis Genesis `yaml:"Genesis"`
|
||||
|
||||
Magic netmode.Magic `yaml:"Magic"`
|
||||
MemPoolSize int `yaml:"MemPoolSize"`
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestProtocolConfigurationValidation(t *testing.T) {
|
||||
|
@ -275,3 +282,85 @@ func TestProtocolConfigurationEquals(t *testing.T) {
|
|||
p.ValidatorsHistory = map[uint32]uint32{112: 0}
|
||||
require.False(t, p.Equals(o))
|
||||
}
|
||||
|
||||
func TestGenesisExtensionsMarshalYAML(t *testing.T) {
|
||||
pk, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub := pk.PublicKey()
|
||||
|
||||
t.Run("MarshalUnmarshalYAML", func(t *testing.T) {
|
||||
g := &Genesis{
|
||||
Roles: map[noderoles.Role]keys.PublicKeys{
|
||||
noderoles.NeoFSAlphabet: {pub},
|
||||
noderoles.P2PNotary: {pub},
|
||||
},
|
||||
Transaction: &GenesisTransaction{
|
||||
Script: []byte{1, 2, 3, 4},
|
||||
SystemFee: 123,
|
||||
},
|
||||
}
|
||||
testserdes.MarshalUnmarshalYAML(t, g, new(Genesis))
|
||||
})
|
||||
|
||||
t.Run("unmarshal config", func(t *testing.T) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
pubStr := hex.EncodeToString(pub.Bytes())
|
||||
script := []byte{1, 2, 3, 4}
|
||||
cfgYml := fmt.Sprintf(`ProtocolConfiguration:
|
||||
Genesis:
|
||||
Transaction:
|
||||
Script: "%s"
|
||||
SystemFee: 123
|
||||
Roles:
|
||||
NeoFSAlphabet:
|
||||
- %s
|
||||
- %s
|
||||
Oracle:
|
||||
- %s
|
||||
- %s`, base64.StdEncoding.EncodeToString(script), pubStr, pubStr, pubStr, pubStr)
|
||||
cfg := new(Config)
|
||||
require.NoError(t, yaml.Unmarshal([]byte(cfgYml), cfg))
|
||||
require.Equal(t, 2, len(cfg.ProtocolConfiguration.Genesis.Roles))
|
||||
require.Equal(t, keys.PublicKeys{pub, pub}, cfg.ProtocolConfiguration.Genesis.Roles[noderoles.NeoFSAlphabet])
|
||||
require.Equal(t, keys.PublicKeys{pub, pub}, cfg.ProtocolConfiguration.Genesis.Roles[noderoles.Oracle])
|
||||
require.Equal(t, &GenesisTransaction{
|
||||
Script: script,
|
||||
SystemFee: 123,
|
||||
}, cfg.ProtocolConfiguration.Genesis.Transaction)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
cfgYml := `ProtocolConfiguration:`
|
||||
cfg := new(Config)
|
||||
require.NoError(t, yaml.Unmarshal([]byte(cfgYml), cfg))
|
||||
require.Nil(t, cfg.ProtocolConfiguration.Genesis.Transaction)
|
||||
require.Empty(t, cfg.ProtocolConfiguration.Genesis.Roles)
|
||||
})
|
||||
|
||||
t.Run("unknown role", func(t *testing.T) {
|
||||
pubStr := hex.EncodeToString(pub.Bytes())
|
||||
cfgYml := fmt.Sprintf(`ProtocolConfiguration:
|
||||
Genesis:
|
||||
Roles:
|
||||
BadRole:
|
||||
- %s`, pubStr)
|
||||
cfg := new(Config)
|
||||
err := yaml.Unmarshal([]byte(cfgYml), cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "unknown node role: BadRole")
|
||||
})
|
||||
|
||||
t.Run("last role", func(t *testing.T) {
|
||||
pubStr := hex.EncodeToString(pub.Bytes())
|
||||
cfgYml := fmt.Sprintf(`ProtocolConfiguration:
|
||||
Genesis:
|
||||
Roles:
|
||||
last:
|
||||
- %s`, pubStr)
|
||||
cfg := new(Config)
|
||||
err := yaml.Unmarshal([]byte(cfgYml), cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "unknown node role: last")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,7 +47,9 @@ import (
|
|||
const (
|
||||
version = "0.2.10"
|
||||
|
||||
defaultInitialGAS = 52000000_00000000
|
||||
// DefaultInitialGAS is the default amount of GAS emitted to the standby validators
|
||||
// multisignature account during native GAS contract initialization.
|
||||
DefaultInitialGAS = 52000000_00000000
|
||||
defaultGCPeriod = 10000
|
||||
defaultMemPoolSize = 50000
|
||||
defaultP2PNotaryRequestPayloadPoolSize = 1000
|
||||
|
@ -228,7 +230,7 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger) (*Bl
|
|||
|
||||
// Protocol configuration fixups/checks.
|
||||
if cfg.InitialGASSupply <= 0 {
|
||||
cfg.InitialGASSupply = fixedn.Fixed8(defaultInitialGAS)
|
||||
cfg.InitialGASSupply = fixedn.Fixed8(DefaultInitialGAS)
|
||||
log.Info("initial gas supply is not set or wrong, setting default value", zap.Stringer("InitialGASSupply", cfg.InitialGASSupply))
|
||||
}
|
||||
if cfg.MemPoolSize <= 0 {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/internal/basicchain"
|
||||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
@ -37,6 +38,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
|
@ -2438,3 +2440,36 @@ func TestBlockchain_ResetState(t *testing.T) {
|
|||
}
|
||||
require.Equal(t, expectedLUB, lub)
|
||||
}
|
||||
|
||||
func TestBlockchain_GenesisTransactionExtension(t *testing.T) {
|
||||
priv0 := testchain.PrivateKeyByID(0)
|
||||
acc0 := wallet.NewAccountFromPrivateKey(priv0)
|
||||
require.NoError(t, acc0.ConvertMultisig(1, []*keys.PublicKey{priv0.PublicKey()}))
|
||||
from := acc0.ScriptHash()
|
||||
to := util.Uint160{1, 2, 3}
|
||||
amount := 1
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, from.BytesBE())
|
||||
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeCheckWitness)
|
||||
emit.Bytes(script.BinWriter, to.BytesBE())
|
||||
emit.Syscall(script.BinWriter, interopnames.SystemRuntimeCheckWitness)
|
||||
emit.AppCall(script.BinWriter, state.CreateNativeContractHash(nativenames.Neo), "transfer", callflag.All, from, to, amount, nil)
|
||||
emit.Opcodes(script.BinWriter, opcode.ASSERT)
|
||||
|
||||
var sysFee int64 = 1_0000_0000
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, func(blockchain *config.Blockchain) {
|
||||
blockchain.Genesis.Transaction = &config.GenesisTransaction{
|
||||
Script: script.Bytes(),
|
||||
SystemFee: sysFee,
|
||||
}
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
b := e.GetBlockByIndex(t, 0)
|
||||
tx := b.Transactions[0]
|
||||
e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true), stackitem.NewBool(false))
|
||||
e.CheckGASBalance(t, e.Validator.ScriptHash(), big.NewInt(core.DefaultInitialGAS-sysFee))
|
||||
actualNeo, lub := e.Chain.GetGoverningTokenBalance(to)
|
||||
require.Equal(t, int64(amount), actualNeo.Int64())
|
||||
require.Equal(t, 0, int(lub))
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256,
|
|||
|
||||
func newBlockCustom(cfg config.ProtocolConfiguration, f func(b *block.Block),
|
||||
txs ...*transaction.Transaction) *block.Block {
|
||||
validators, _ := validatorsFromConfig(cfg)
|
||||
validators, _, _ := validatorsFromConfig(cfg)
|
||||
valScript, _ := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
|
||||
witness := transaction.Witness{
|
||||
VerificationScript: valScript,
|
||||
|
|
|
@ -87,7 +87,7 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts {
|
|||
cs.Policy = policy
|
||||
cs.Contracts = append(cs.Contracts, neo, gas, policy)
|
||||
|
||||
desig := newDesignate(cfg.P2PSigExtensions)
|
||||
desig := newDesignate(cfg.P2PSigExtensions, cfg.Genesis.Roles)
|
||||
desig.NEO = neo
|
||||
cs.Designate = desig
|
||||
cs.Contracts = append(cs.Contracts, desig)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
@ -32,6 +33,9 @@ type Designate struct {
|
|||
|
||||
// p2pSigExtensionsEnabled defines whether the P2P signature extensions logic is relevant.
|
||||
p2pSigExtensionsEnabled bool
|
||||
// initialNodeRoles defines a set of node roles that should be defined at the contract
|
||||
// deployment (initialization).
|
||||
initialNodeRoles map[noderoles.Role]keys.PublicKeys
|
||||
|
||||
OracleService atomic.Value
|
||||
// NotaryService represents a Notary node module.
|
||||
|
@ -97,9 +101,10 @@ func (s *Designate) isValidRole(r noderoles.Role) bool {
|
|||
r == noderoles.NeoFSAlphabet || (s.p2pSigExtensionsEnabled && r == noderoles.P2PNotary)
|
||||
}
|
||||
|
||||
func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
|
||||
func newDesignate(p2pSigExtensionsEnabled bool, initialNodeRoles map[noderoles.Role]keys.PublicKeys) *Designate {
|
||||
s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation, designateContractID)}
|
||||
s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled
|
||||
s.initialNodeRoles = initialNodeRoles
|
||||
defer s.UpdateHash()
|
||||
|
||||
desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType,
|
||||
|
@ -127,6 +132,19 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
|
|||
func (s *Designate) Initialize(ic *interop.Context) error {
|
||||
cache := &DesignationCache{}
|
||||
ic.DAO.SetCache(s.ID, cache)
|
||||
|
||||
if len(s.initialNodeRoles) != 0 {
|
||||
for _, r := range noderoles.Roles {
|
||||
pubs, ok := s.initialNodeRoles[r]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := s.DesignateAsRole(ic, r, pubs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize Designation role data for role %s: %w", r, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -355,10 +373,14 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
|
|||
if !s.isValidRole(r) {
|
||||
return ErrInvalidRole
|
||||
}
|
||||
|
||||
if ic.Trigger != trigger.OnPersist {
|
||||
h := s.NEO.GetCommitteeAddress(ic.DAO)
|
||||
if ok, err := runtime.CheckHashedWitness(ic, h); err != nil || !ok {
|
||||
return ErrInvalidWitness
|
||||
}
|
||||
}
|
||||
|
||||
if ic.Block == nil {
|
||||
return ErrNoBlock
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package native_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
|
@ -137,3 +140,23 @@ func TestDesignate_Cache(t *testing.T) {
|
|||
require.Nil(t, updatedNodes)
|
||||
require.False(t, updateCalled)
|
||||
}
|
||||
|
||||
func TestDesignate_GenesisRolesExtension(t *testing.T) {
|
||||
pk1, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pk2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubs := keys.PublicKeys{pk1.PublicKey(), pk2.PublicKey()}
|
||||
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, func(blockchain *config.Blockchain) {
|
||||
blockchain.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{
|
||||
noderoles.StateValidator: pubs,
|
||||
}
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
|
||||
|
||||
// Check designated node in a separate block.
|
||||
sort.Sort(pubs)
|
||||
checkNodeRoles(t, c, true, noderoles.StateValidator, e.Chain.BlockHeight()+1, pubs)
|
||||
}
|
||||
|
|
39
pkg/core/native/noderoles/role.go
Normal file
39
pkg/core/native/noderoles/role.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package noderoles
|
||||
|
||||
//go:generate stringer -type=Role
|
||||
|
||||
// Role represents the type of the participant.
|
||||
type Role byte
|
||||
|
||||
// Role enumeration.
|
||||
const (
|
||||
_ Role = 1 << iota
|
||||
_
|
||||
StateValidator
|
||||
Oracle
|
||||
NeoFSAlphabet
|
||||
P2PNotary
|
||||
// last denotes the end of roles enum. Consider adding new roles before the last.
|
||||
last
|
||||
)
|
||||
|
||||
// Roles is a set of all available roles sorted by values.
|
||||
var Roles []Role
|
||||
|
||||
// roles is a map of valid Role string representation to its type.
|
||||
var roles map[string]Role
|
||||
|
||||
func init() {
|
||||
roles = make(map[string]Role)
|
||||
for i := StateValidator; i < last; i = i << 1 {
|
||||
roles[i.String()] = i
|
||||
Roles = append(Roles, i)
|
||||
}
|
||||
}
|
||||
|
||||
// FromString returns a node role parsed from its string representation and a
|
||||
// boolean value denoting whether the conversion was OK and the role exists.
|
||||
func FromString(s string) (Role, bool) {
|
||||
r, ok := roles[s]
|
||||
return r, ok
|
||||
}
|
41
pkg/core/native/noderoles/role_string.go
Normal file
41
pkg/core/native/noderoles/role_string.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Code generated by "stringer -type=Role"; DO NOT EDIT.
|
||||
|
||||
package noderoles
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[StateValidator-4]
|
||||
_ = x[Oracle-8]
|
||||
_ = x[NeoFSAlphabet-16]
|
||||
_ = x[P2PNotary-32]
|
||||
_ = x[last-64]
|
||||
}
|
||||
|
||||
const (
|
||||
_Role_name_0 = "StateValidator"
|
||||
_Role_name_1 = "Oracle"
|
||||
_Role_name_2 = "NeoFSAlphabet"
|
||||
_Role_name_3 = "P2PNotary"
|
||||
_Role_name_4 = "last"
|
||||
)
|
||||
|
||||
func (i Role) String() string {
|
||||
switch {
|
||||
case i == 4:
|
||||
return _Role_name_0
|
||||
case i == 8:
|
||||
return _Role_name_1
|
||||
case i == 16:
|
||||
return _Role_name_2
|
||||
case i == 32:
|
||||
return _Role_name_3
|
||||
case i == 64:
|
||||
return _Role_name_4
|
||||
default:
|
||||
return "Role(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
27
pkg/core/native/noderoles/role_test.go
Normal file
27
pkg/core/native/noderoles/role_test.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package noderoles
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFromString(t *testing.T) {
|
||||
valid := map[string]Role{
|
||||
"StateValidator": StateValidator,
|
||||
"Oracle": Oracle,
|
||||
"NeoFSAlphabet": NeoFSAlphabet,
|
||||
"P2PNotary": P2PNotary,
|
||||
}
|
||||
for s, expected := range valid {
|
||||
actual, ok := FromString(s)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
invalid := []string{"last", "InvalidRole"}
|
||||
for _, s := range invalid {
|
||||
_, ok := FromString(s)
|
||||
require.False(t, ok)
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package noderoles
|
||||
|
||||
// Role represents the type of the participant.
|
||||
type Role byte
|
||||
|
||||
// Role enumeration.
|
||||
const (
|
||||
StateValidator Role = 4
|
||||
Oracle Role = 8
|
||||
NeoFSAlphabet Role = 16
|
||||
P2PNotary Role = 32
|
||||
)
|
|
@ -24,7 +24,7 @@ func TestDesignate_DesignateAsRole(t *testing.T) {
|
|||
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
||||
bl := block.New(bc.config.StateRootInHeader)
|
||||
bl.Index = bc.BlockHeight() + 1
|
||||
ic := bc.newInteropContext(trigger.OnPersist, bc.dao, bl, tx)
|
||||
ic := bc.newInteropContext(trigger.Application, bc.dao, bl, tx)
|
||||
ic.SpawnVM()
|
||||
ic.VM.LoadScript([]byte{byte(opcode.RET)})
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ var (
|
|||
|
||||
func (i WitnessScope) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 1:
|
||||
case i <= 1:
|
||||
return _WitnessScope_name_0[_WitnessScope_index_0[i]:_WitnessScope_index_0[i+1]]
|
||||
case i == 16:
|
||||
return _WitnessScope_name_1
|
||||
|
|
|
@ -34,7 +34,7 @@ var (
|
|||
|
||||
func (i WitnessConditionType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 3:
|
||||
case i <= 3:
|
||||
return _WitnessConditionType_name_0[_WitnessConditionType_index_0[i]:_WitnessConditionType_index_0[i+1]]
|
||||
case 24 <= i && i <= 25:
|
||||
i -= 24
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
|
@ -15,7 +16,7 @@ import (
|
|||
|
||||
// CreateGenesisBlock creates a genesis block based on the given configuration.
|
||||
func CreateGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
|
||||
validators, err := validatorsFromConfig(cfg)
|
||||
validators, committee, err := validatorsFromConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -25,6 +26,49 @@ func CreateGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
txs := []*transaction.Transaction{}
|
||||
if cfg.Genesis.Transaction != nil {
|
||||
committeeH, err := getCommitteeAddress(committee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate committee address: %w", err)
|
||||
}
|
||||
tx := cfg.Genesis.Transaction
|
||||
signers := []transaction.Signer{
|
||||
{
|
||||
Account: nextConsensus,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
}
|
||||
scripts := []transaction.Witness{
|
||||
{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{byte(opcode.PUSH1)},
|
||||
},
|
||||
}
|
||||
if !committeeH.Equals(nextConsensus) {
|
||||
signers = append(signers, []transaction.Signer{
|
||||
{
|
||||
Account: committeeH,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
}...)
|
||||
scripts = append(scripts, []transaction.Witness{
|
||||
{
|
||||
InvocationScript: []byte{},
|
||||
VerificationScript: []byte{byte(opcode.PUSH1)},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
txs = append(txs, &transaction.Transaction{
|
||||
SystemFee: tx.SystemFee,
|
||||
ValidUntilBlock: 1,
|
||||
Script: tx.Script,
|
||||
Signers: signers,
|
||||
Scripts: scripts,
|
||||
})
|
||||
}
|
||||
|
||||
base := block.Header{
|
||||
Version: 0,
|
||||
PrevHash: util.Uint256{},
|
||||
|
@ -41,19 +85,19 @@ func CreateGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
|
|||
|
||||
b := &block.Block{
|
||||
Header: base,
|
||||
Transactions: []*transaction.Transaction{},
|
||||
Transactions: txs,
|
||||
}
|
||||
b.RebuildMerkleRoot()
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {
|
||||
func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, []*keys.PublicKey, error) {
|
||||
vs, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return vs[:cfg.GetNumOfCNs(0)], nil
|
||||
return vs.Copy()[:cfg.GetNumOfCNs(0)], vs, nil
|
||||
}
|
||||
|
||||
func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, err error) {
|
||||
|
@ -64,6 +108,14 @@ func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, er
|
|||
return hash.Hash160(raw), nil
|
||||
}
|
||||
|
||||
func getCommitteeAddress(committee []*keys.PublicKey) (val util.Uint160, err error) {
|
||||
raw, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
return hash.Hash160(raw), nil
|
||||
}
|
||||
|
||||
// hashSliceReverse reverses the given slice of util.Uint256.
|
||||
func hashSliceReverse(dest []util.Uint256) {
|
||||
for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 {
|
||||
|
|
|
@ -30,7 +30,7 @@ func TestGetConsensusAddressMainNet(t *testing.T) {
|
|||
cfg, err := config.Load("../../config", netmode.MainNet)
|
||||
require.NoError(t, err)
|
||||
|
||||
validators, err := validatorsFromConfig(cfg.ProtocolConfiguration)
|
||||
validators, _, err := validatorsFromConfig(cfg.ProtocolConfiguration)
|
||||
require.NoError(t, err)
|
||||
|
||||
script, err := getNextConsensusAddress(validators)
|
||||
|
|
|
@ -390,3 +390,23 @@ func (p *PublicKey) UnmarshalJSON(data []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements the YAML marshaler interface.
|
||||
func (p *PublicKey) MarshalYAML() (any, error) {
|
||||
return hex.EncodeToString(p.Bytes()), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the YAML unmarshaler interface.
|
||||
func (p *PublicKey) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var s string
|
||||
err := unmarshal(&s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode public key from hex bytes: %w", err)
|
||||
}
|
||||
return p.DecodeBytes(b)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/internal/testserdes"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeInfinity(t *testing.T) {
|
||||
|
@ -246,3 +247,61 @@ func BenchmarkPublicDecodeBytes(t *testing.B) {
|
|||
require.NoError(t, k.DecodeBytes(keyBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshallYAML(t *testing.T) {
|
||||
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
||||
pubKey, err := NewPublicKeyFromString(str)
|
||||
require.NoError(t, err)
|
||||
|
||||
bytes, err := yaml.Marshal(&pubKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []byte(str + "\n") // YAML marshaller adds new line in the end which is expected.
|
||||
require.Equal(t, expected, bytes)
|
||||
}
|
||||
|
||||
func TestUnmarshallYAML(t *testing.T) {
|
||||
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
||||
expected, err := NewPublicKeyFromString(str)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := &PublicKey{}
|
||||
err = yaml.Unmarshal([]byte(str), actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestUnmarshallYAMLBadCompresed(t *testing.T) {
|
||||
str := `"02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`
|
||||
actual := &PublicKey{}
|
||||
err := yaml.Unmarshal([]byte(str), actual)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "error computing Y for compressed point")
|
||||
}
|
||||
|
||||
func TestUnmarshallYAMLNotAHex(t *testing.T) {
|
||||
str := `"04Tb17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"`
|
||||
actual := &PublicKey{}
|
||||
err := yaml.Unmarshal([]byte(str), actual)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "failed to decode public key from hex bytes")
|
||||
}
|
||||
|
||||
func TestUnmarshallYAMLUncompressed(t *testing.T) {
|
||||
str := "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"
|
||||
expected, err := NewPublicKeyFromString(str)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := &PublicKey{}
|
||||
err = yaml.Unmarshal([]byte(str), actual)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalYAML(t *testing.T) {
|
||||
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
|
||||
expected, err := NewPublicKeyFromString(str)
|
||||
require.NoError(t, err)
|
||||
|
||||
testserdes.MarshalUnmarshalYAML(t, expected, new(PublicKey))
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ var (
|
|||
|
||||
func (i CommandType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 1:
|
||||
case i <= 1:
|
||||
return _CommandType_name_0[_CommandType_index_0[i]:_CommandType_index_0[i+1]]
|
||||
case 16 <= i && i <= 17:
|
||||
i -= 16
|
||||
|
|
Loading…
Reference in a new issue