Skip to content

Commit

Permalink
Introduce procedure for the NeoFS Sidechain deployment
Browse files Browse the repository at this point in the history
There is a need to initialize running Neo network intended to be NeoFS
Sidechain in automatic mode. To do this, a group of committee nodes
working in the NeoFS Inner Ring should conduct a set of operations on
the blockchain.

This commit introduces partially implemented deployment procedure incl.
NNS deployment, Notary service and committee group initialization.
Deployment of NeoFS-system and custom smart contracts will be
implemented in the future. Since procedure is incomplete, it's not yet
used in the Inner Ring application.

Refs #2195.

Signed-off-by: Leonard Lyubich <[email protected]>
  • Loading branch information
cthulhu-rider committed Jun 24, 2023
1 parent 85a31ff commit 2014eb3
Show file tree
Hide file tree
Showing 8 changed files with 1,849 additions and 1 deletion.
2 changes: 1 addition & 1 deletion contracts/00-nns.manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":2567,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":568,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":479,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":2702,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":2858,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":false},{"name":"getPrice","offset":972,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":2659,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1006,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":501,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":523,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1267,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"renew","offset":2026,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":2836,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":866,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2237,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":894,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2371,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":473,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":644,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":673,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":485,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":735,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":386,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2147,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":481,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null}
{"name":"NameService","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":32,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"addRecord","offset":3046,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"balanceOf","offset":798,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"decimals","offset":613,"parameters":[],"returntype":"Integer","safe":true},{"name":"deleteRecords","offset":3270,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Void","safe":false},{"name":"getAllRecords","offset":3514,"parameters":[{"name":"name","type":"String"}],"returntype":"InteropInterface","safe":false},{"name":"getPrice","offset":1249,"parameters":[],"returntype":"Integer","safe":true},{"name":"getRecords","offset":3186,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"isAvailable","offset":1283,"parameters":[{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"ownerOf","offset":635,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Hash160","safe":true},{"name":"properties","offset":705,"parameters":[{"name":"tokenID","type":"ByteArray"}],"returntype":"Map","safe":true},{"name":"register","offset":1544,"parameters":[{"name":"name","type":"String"},{"name":"owner","type":"Hash160"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerTLD","offset":2219,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"renew","offset":2420,"parameters":[{"name":"name","type":"String"}],"returntype":"Integer","safe":false},{"name":"resolve","offset":3452,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"}],"returntype":"Array","safe":true},{"name":"roots","offset":1143,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"setAdmin","offset":2631,"parameters":[{"name":"name","type":"String"},{"name":"admin","type":"Hash160"}],"returntype":"Void","safe":false},{"name":"setPrice","offset":1171,"parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","safe":false},{"name":"setRecord","offset":2806,"parameters":[{"name":"name","type":"String"},{"name":"typ","type":"Integer"},{"name":"id","type":"Integer"},{"name":"data","type":"String"}],"returntype":"Void","safe":false},{"name":"symbol","offset":607,"parameters":[],"returntype":"String","safe":true},{"name":"tokens","offset":874,"parameters":[],"returntype":"InteropInterface","safe":true},{"name":"tokensOf","offset":903,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"InteropInterface","safe":true},{"name":"totalSupply","offset":619,"parameters":[],"returntype":"Integer","safe":true},{"name":"transfer","offset":965,"parameters":[{"name":"to","type":"Hash160"},{"name":"tokenID","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Boolean","safe":false},{"name":"update","offset":520,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"String"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"updateSOA","offset":2541,"parameters":[{"name":"name","type":"String"},{"name":"email","type":"String"},{"name":"refresh","type":"Integer"},{"name":"retry","type":"Integer"},{"name":"expire","type":"Integer"},{"name":"ttl","type":"Integer"}],"returntype":"Void","safe":false},{"name":"version","offset":615,"parameters":[],"returntype":"Integer","safe":true}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenId","type":"ByteArray"}]}]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]},{"contract":"*","methods":["onNEP11Payment"]}],"supportedstandards":["NEP-11"],"trusts":[],"extra":null}
Binary file modified contracts/00-nns.nef
Binary file not shown.
205 changes: 205 additions & 0 deletions pkg/morph/deploy/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Package deploy provides NeoFS Sidechain deployment functionality.
package deploy

import (
"context"
"errors"
"fmt"
"sort"

"github.com/nspcc-dev/neo-go/pkg/core/block"
"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/neorpc"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"go.uber.org/zap"
)

// Blockchain groups services provided by particular Neo blockchain network
// representing NeoFS Sidechain that are required for its deployment.
type Blockchain interface {
// RPCActor groups functions needed to compose and send transactions to the
// blockchain.
actor.RPCActor

// GetCommittee returns list of public keys owned by Neo blockchain committee
// members. Resulting list is non-empty, unique and unsorted.
GetCommittee() (keys.PublicKeys, error)

// GetContractStateByID returns network state of the smart contract by its ID.
// GetContractStateByID returns error with 'Unknown contract' substring if
// requested contract is missing.
GetContractStateByID(id int32) (*state.Contract, error)

// ReceiveBlocks starts background process that forwards new blocks of the
// blockchain to the provided channel. The process handles all new blocks when
// ReceiveBlocks is called with nil filter. Returns unique identifier to be used
// to stop the process via Unsubscribe.
ReceiveBlocks(*neorpc.BlockFilter, chan<- *block.Block) (id string, err error)

// Unsubscribe stops background process started by ReceiveBlocks by ID.
Unsubscribe(id string) error
}

// KeyStorage represents storage of the private keys.
type KeyStorage interface {
// GetPersistedPrivateKey returns singleton private key persisted in the
// storage. GetPersistedPrivateKey randomizes the key initially. All subsequent
// successful calls return the same key.
GetPersistedPrivateKey() (*keys.PrivateKey, error)
}

// CommonDeployPrm groups common deployment parameters of the smart contract.
type CommonDeployPrm struct {
NEF nef.File
Manifest manifest.Manifest
}

// NNSPrm groups deployment parameters of the NeoFS NNS contract.
type NNSPrm struct {
Common CommonDeployPrm
SystemEmail string
}

// Prm groups all parameters of the NeoFS Sidechain deployment procedure.
type Prm struct {
// Writes progress into the log.
Logger *zap.Logger

// Particular Neo blockchain instance to be used as NeoFS Sidechain.
Blockchain Blockchain

// Local process account used for transaction signing (must be unlocked).
LocalAccount *wallet.Account

// Storage for single committee group key.
KeyStorage KeyStorage

NNS NNSPrm
}

// Deploy initializes Neo network represented by given Prm.Blockchain as NeoFS
// Sidechain and makes it full-featured for NeoFS storage system operation.
//
// Deploy aborts only by context or when a fatal error occurs. Deployment
// progress is logged in detail. It is expected that some situations can be
// changed/fixed on the chain from the outside, so Deploy adapts flexibly and
// does not stop at the moment.
//
// Deployment process is detailed in NeoFS docs. Summary of stages:
// 1. NNS contract deployment
// 2. launch of a notary service for the committee
// 3. committee group initialization
// 4. deployment of the NeoFS system contracts (currently not done)
// 5. deployment of custom contracts
//
// See project documentation for details.
func Deploy(ctx context.Context, prm Prm) error {
committee, err := prm.Blockchain.GetCommittee()
if err != nil {
return fmt.Errorf("get Neo committee of the network: %w", err)
}

sort.Sort(committee)

// determine a leader
localPrivateKey := prm.LocalAccount.PrivateKey()
localPublicKey := localPrivateKey.PublicKey()
localAccCommitteeIndex := -1

for i := range committee {
if committee[i].Equal(localPublicKey) {
localAccCommitteeIndex = i
break
}
}

if localAccCommitteeIndex < 0 {
return errors.New("local account does not belong to any Neo committee member")
}

deployNNSPrm := deployNNSContractPrm{
logger: prm.Logger,
blockchain: prm.Blockchain,
localAcc: prm.LocalAccount,
localNEF: prm.NNS.Common.NEF,
localManifest: prm.NNS.Common.Manifest,
systemEmail: prm.NNS.SystemEmail,
initCommitteeGroupKey: nil, // set below
}

// if local node is the first committee member (Az) => deploy NNS contract,
// otherwise just wait
if localAccCommitteeIndex == 0 {
// Why such a centralized approach? There is a need to initialize committee
// contract group and share its private key between all committee members (the
// latter is done in the current procedure next). Currently, there is no
// convenient Neo service for this, and we don't want to use anything but
// blockchain, so the key is distributed through domain NNS records. However,
// then the chicken-and-egg problem pops up: committee group must be also set
// for the NNS contract. To set the group, you need to know the contract hash in
// advance, and it is a function from the sender of the deployment transaction.
// Summing up all these statements, we come to the conclusion that the one who
// deploys the contract creates the group key, and he shares it among the other
// members. Technically any committee member could deploy NNS contract, but for
// the sake of simplicity, this is a fixed node. This makes the procedure even
// more centralized, however, in practice, at the start of the network, all
// members are expected to be healthy and active.
//
// Note that manifest can't be changed w/o NEF change, so it's impossible to set
// committee group dynamically right after deployment. See
// https://github.com/nspcc-dev/neofs-contract/issues/340
deployNNSPrm.initCommitteeGroupKey = prm.KeyStorage.GetPersistedPrivateKey
}

prm.Logger.Info("initializing NNS contract on the chain...")

nnsOnChainAddress, err := initNNSContract(ctx, deployNNSPrm)
if err != nil {
return fmt.Errorf("init NNS contract on the chain: %w", err)
}

prm.Logger.Info("NNS contract successfully initialized on the chain", zap.Stringer("address", nnsOnChainAddress))

prm.Logger.Info("enable Notary service for the committee...")

err = enableNotary(ctx, enableNotaryPrm{
logger: prm.Logger,
blockchain: prm.Blockchain,
nnsOnChainAddress: nnsOnChainAddress,
systemEmail: prm.NNS.SystemEmail,
committee: committee,
localAcc: prm.LocalAccount,
localAccCommitteeIndex: localAccCommitteeIndex,
})
if err != nil {
return fmt.Errorf("enable Notary service for the committee: %w", err)
}

prm.Logger.Info("Notary service successfully enabled for the committee")

prm.Logger.Info("initializing committee group for contract management...")

committeeGroupKey, err := initCommitteeGroup(ctx, initCommitteeGroupPrm{
logger: prm.Logger,
blockchain: prm.Blockchain,
nnsOnChainAddress: nnsOnChainAddress,
systemEmail: prm.NNS.SystemEmail,
committee: committee,
localAcc: prm.LocalAccount,
localAccCommitteeIndex: localAccCommitteeIndex,
keyStorage: prm.KeyStorage,
})
if err != nil {
return fmt.Errorf("init committee group: %w", err)
}

prm.Logger.Info("committee group successfully initialized", zap.Stringer("public key", committeeGroupKey.PublicKey()))

// TODO: deploy contracts

return nil
}
Loading

0 comments on commit 2014eb3

Please sign in to comment.