diff --git a/contracts/00-nns.manifest.json b/contracts/00-nns.manifest.json index b858bab5199..16a6183e571 100755 --- a/contracts/00-nns.manifest.json +++ b/contracts/00-nns.manifest.json @@ -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} \ No newline at end of file +{"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} \ No newline at end of file diff --git a/contracts/00-nns.nef b/contracts/00-nns.nef index 85c9a45afe6..39a0058416b 100755 Binary files a/contracts/00-nns.nef and b/contracts/00-nns.nef differ diff --git a/pkg/morph/deploy/alphabet.go b/pkg/morph/deploy/alphabet.go index 5ac56f88e8d..638eeba1945 100644 --- a/pkg/morph/deploy/alphabet.go +++ b/pkg/morph/deploy/alphabet.go @@ -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" ) @@ -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) + } +} diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go index f9221a4cfdd..8fb9f7c26d3 100644 --- a/pkg/morph/deploy/deploy.go +++ b/pkg/morph/deploy/deploy.go @@ -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) // @@ -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 diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go index 44c944239cb..04c44f71620 100644 --- a/pkg/morph/deploy/notary.go +++ b/pkg/morph/deploy/notary.go @@ -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 @@ -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()) @@ -905,7 +906,7 @@ func _newCustomCommitteeNotaryActor( fCommitteeSigner(&committeeSignerAcc.Signer) - return notary.NewActor(b, []actor.SignerAccount{ + signers := []actor.SignerAccount{ { Signer: transaction.Signer{ Account: payerAcc.ScriptHash(), @@ -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