Skip to content

Commit

Permalink
sidechain/deploy: Register Alphabet members as candidates to validators
Browse files Browse the repository at this point in the history
All NeoFS Alphabet (i.e. committee) members must be registered as
candidates to Sidechain validators. This is vital for NeoFS network
processing.

Extended Sidechain deployment procedure with registration stage. It
sends one transaction witnessed by committee multi-sig (to temporary
minimize registration price) and each individual Alphabet member
(required by Neo contract).

Signed-off-by: Leonard Lyubich <[email protected]>
  • Loading branch information
cthulhu-rider committed Aug 24, 2023
1 parent 48c8ab5 commit 95ba5b1
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 8 deletions.
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.
198 changes: 198 additions & 0 deletions pkg/morph/deploy/alphabet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ package deploy

import (
"context"
"errors"
"fmt"

"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -93,3 +100,194 @@ func initAlphabet(ctx context.Context, prm initAlphabetPrm) error {
txMonitor.trackPendingTransactionsAsync(ctx, vub, mainTxID, fallbackTxID)
}
}

// groups parameters of initVoteForAlphabet.
type initVoteForAlphabetPrm struct {
logger *zap.Logger

blockchain Blockchain

// based on blockchain
monitor *blockchainMonitor

committee keys.PublicKeys
localAcc *wallet.Account

// pays for Notary transactions
proxyContract util.Uint160
}

// initializes vote for NeoFS Alphabet members for the role of validators.
func initVoteForAlphabet(ctx context.Context, prm initVoteForAlphabetPrm) error {
committeeActor, err := newProxyCommitteeNotaryActor(prm.blockchain, prm.localAcc, prm.committee, prm.proxyContract)
if err != nil {
return fmt.Errorf("create Notary service client sending transactions to be signed by the committee: %w", err)
}

roleContract := rolemgmt.NewReader(committeeActor)

alphabet, err := roleContract.GetDesignatedByRole(noderoles.NeoFSAlphabet, prm.monitor.currentHeight())
if err != nil {
return fmt.Errorf("request NeoFS Alphabet members: %w", err)
}

if len(alphabet) == 0 {
return errors.New("no NeoFS Alphabet members are set")
}

// wrap the parent context into the context of the current function so that
// transaction wait routines do not leak
ctx, cancel := context.WithCancel(ctx)
defer cancel()

neoContract := neo.New(committeeActor)
txMonitor := newTransactionGroupMonitor(committeeActor)
mRegisteredAlphabetIndices := make(map[int]struct{}, len(alphabet))
var originalPrice int64
scriptBuilder := smartcontract.NewBuilder()
setRegisterPrice := func(price int64) { scriptBuilder.InvokeMethod(neo.Hash, "setRegisterPrice", price) }

upperLoop:
for ; ; prm.monitor.waitForNextBlock(ctx) {
select {
case <-ctx.Done():
return fmt.Errorf("wait for NeoFS Alphabet to be registered as candidates to validators: %w", ctx.Err())
default:
}

prm.logger.Info("checking registered candidates to validators...")

iterCandidates, err := neoContract.GetAllCandidates()
if err != nil {
prm.logger.Error("failed to init iterator over registered candidates to validators, will try again later", zap.Error(err))
continue
}

for k := range mRegisteredAlphabetIndices {
delete(mRegisteredAlphabetIndices, k)
}

for {
candidates, err := iterCandidates.Next(len(alphabet))
if err != nil {
prm.logger.Info("failed to get next bunch of registered candidates to validators, will try again later",
zap.Error(err))
continue upperLoop
}

loop:
for i := range candidates {
for j := range alphabet {
if _, ok := mRegisteredAlphabetIndices[i]; !ok {
if candidates[i].PublicKey.Equal(alphabet[j]) {
mRegisteredAlphabetIndices[j] = struct{}{}
continue loop
}
}
}
}

if len(candidates) == 0 {
err = iterCandidates.Terminate()
if err != nil {
prm.logger.Info("failed to terminate iterator over registered candidates to validators, ignore",
zap.Error(err))
}

break
}
}

if len(mRegisteredAlphabetIndices) == len(alphabet) {
prm.logger.Info("all NeoFS Alphabet members are registered as candidates to validators")
return nil
}

prm.logger.Info("not all members of the NeoFS Alphabet are candidates to validators, registration is needed")

if txMonitor.isPending() {
prm.logger.Info("previously sent Notary request registering NeoFS Alphabet members as candidates to validators is still pending, will wait for the outcome")
continue
}

originalPrice, err = neoContract.GetRegisterPrice()
if err != nil {
prm.logger.Info("failed to get original candidate registration price, will try again later",
zap.Error(err))
continue
}

scriptBuilder.Reset()

const minPrice = 1 // 0 is forbidden
if originalPrice > minPrice {
setRegisterPrice(minPrice)
}

for i := range alphabet {
if _, ok := mRegisteredAlphabetIndices[i]; ok {
continue
}

prm.logger.Info("NeoFS Alphabet members is not yet a candidate to validators, going to register")

scriptBuilder.InvokeWithAssert(neo.Hash, "registerCandidate", alphabet[i].Bytes())
}

if originalPrice > minPrice {
setRegisterPrice(originalPrice)
}

script, err := scriptBuilder.Script()
if err != nil {
prm.logger.Info("failed to build script registering NeoFS Alphabet members as validators, will try again later",
zap.Error(err))
continue
}

candidateSigners := make([]actor.SignerAccount, 0, len(alphabet)-len(mRegisteredAlphabetIndices))

for i := range alphabet {
if _, ok := mRegisteredAlphabetIndices[i]; ok {
continue
}

var acc *wallet.Account
if alphabet[i].Equal(prm.localAcc.PublicKey()) {
acc = prm.localAcc
} else {
acc = notary.FakeSimpleAccount(alphabet[i])
}
candidateSigners = append(candidateSigners, actor.SignerAccount{
Signer: transaction.Signer{
Account: alphabet[i].GetScriptHash(),
Scopes: transaction.CustomContracts,
AllowedContracts: []util.Uint160{neo.Hash},
},
Account: acc,
})
}

curActor, err := newProxyCommitteeNotaryActor(prm.blockchain, prm.localAcc, prm.committee, prm.proxyContract, candidateSigners...)
if err != nil {
prm.logger.Error("failed to make Notary actor with candidate signers, will try again later",
zap.Error(err))
continue
}

mainTxID, fallbackTxID, vub, err := curActor.Notarize(curActor.MakeRun(script))
if err != nil {
if isErrNotEnoughGAS(err) {
prm.logger.Info("insufficient Notary balance to send new Notary request registering NeoFS Alphabet members as validators, skip")
} else {
prm.logger.Error("failed to send new Notary request registering NeoFS Alphabet members as validators, skip", zap.Error(err))
}
continue
}

prm.logger.Info("Notary request registering NeoFS Alphabet members as validators has been successfully sent, will wait for the outcome",
zap.Stringer("main tx", mainTxID), zap.Stringer("fallback tx", fallbackTxID), zap.Uint32("vub", vub))

txMonitor.trackPendingTransactionsAsync(ctx, vub, mainTxID, fallbackTxID)
}
}
18 changes: 17 additions & 1 deletion pkg/morph/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ type Prm struct {
// 2. launch of a notary service for the committee
// 3. initial GAS distribution between committee members
// 4. committee group initialization
// 5. Alphabet initialization
// 5. Alphabet initialization (incl. registration as candidates to validators)
// 6. deployment/update of the NeoFS system contracts
// 7. deployment of custom contracts (currently not supported)
//
Expand Down Expand Up @@ -461,6 +461,22 @@ func Deploy(ctx context.Context, prm Prm) error {

prm.Logger.Info("Proxy balance successfully replenished")

prm.Logger.Info("initializing vote for NeoFS Alphabet members to role of validators...")

err = initVoteForAlphabet(ctx, initVoteForAlphabetPrm{
logger: prm.Logger,
blockchain: prm.Blockchain,
monitor: monitor,
committee: committee,
localAcc: prm.LocalAccount,
proxyContract: proxyContractAddress,
})
if err != nil {
return fmt.Errorf("init vote for NeoFS Alphabet members to role of validators: %w", err)
}

prm.Logger.Info("vote for NeoFS Alphabet to role of validators successfully initialized")

// NNS (update)
//
// Special contract which is always deployed first, but its update depends on
Expand Down
15 changes: 9 additions & 6 deletions pkg/morph/deploy/notary.go
Original file line number Diff line number Diff line change
Expand Up @@ -863,17 +863,17 @@ func newCommitteeNotaryActorWithCustomCommitteeSigner(
// returns notary.Actor that builds and sends Notary service requests witnessed
// by the specified committee members to the provided Blockchain. Local account
// should be one of the committee members. Given Proxy contract pays for main
// transactions.
func newProxyCommitteeNotaryActor(b Blockchain, localAcc *wallet.Account, committee keys.PublicKeys, proxyContract util.Uint160) (*notary.Actor, error) {
// transactions. Allows to specify extra transaction signers.
func newProxyCommitteeNotaryActor(b Blockchain, localAcc *wallet.Account, committee keys.PublicKeys, proxyContract util.Uint160, extraSigners ...actor.SignerAccount) (*notary.Actor, error) {
return _newCustomCommitteeNotaryActor(b, localAcc, committee, notary.FakeContractAccount(proxyContract), func(s *transaction.Signer) {
s.Scopes = transaction.CalledByEntry
})
}, extraSigners...)
}

// returns notary.Actor builds and sends Notary service requests witnessed by
// the specified committee members to the provided Blockchain. Local account
// should be one of the committee members. Specified account pays for
// main transactions.
// main transactions. Allows to specify extra transaction signers.
//
// Transaction signer callback allows to specify committee signer (e.g. tune
// witness scope). Instance passed to it has Account set to multi-signature
Expand All @@ -887,6 +887,7 @@ func _newCustomCommitteeNotaryActor(
committee keys.PublicKeys,
payerAcc *wallet.Account,
fCommitteeSigner func(*transaction.Signer),
extraSigners ...actor.SignerAccount,
) (*notary.Actor, error) {
committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(committee))
committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(localAcc.PrivateKey())
Expand All @@ -905,7 +906,7 @@ func _newCustomCommitteeNotaryActor(

fCommitteeSigner(&committeeSignerAcc.Signer)

return notary.NewActor(b, []actor.SignerAccount{
signers := []actor.SignerAccount{
{
Signer: transaction.Signer{
Account: payerAcc.ScriptHash(),
Expand All @@ -914,7 +915,9 @@ func _newCustomCommitteeNotaryActor(
Account: payerAcc,
},
committeeSignerAcc,
}, localAcc)
}

return notary.NewActor(b, append(signers, extraSigners...), localAcc)
}

// Amount of GAS for the single local account's GAS->Notary transfer. Relatively
Expand Down

0 comments on commit 95ba5b1

Please sign in to comment.