From 335db5af9c1e0eaebacedf98798371b476301561 Mon Sep 17 00:00:00 2001 From: Oleg Kulachenko Date: Sat, 3 Jun 2023 20:29:56 +0400 Subject: [PATCH 1/9] Debug tests Signed-off-by: Oleg Kulachenko --- .github/workflows/run-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 153b314cf07..2b67204b977 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,8 +31,8 @@ jobs: - name: Checkout neofs-testcases repository uses: actions/checkout@v3 with: - repository: nspcc-dev/neofs-testcases - ref: 'master' + repository: vvarg-229/neofs-testcases + ref: 'tst-skip' path: neofs-testcases - name: Checkout neofs-dev-env repository From fcb2ff19a0fb6ae277813e32c868eda290cff06e Mon Sep 17 00:00:00 2001 From: Oleg Kulachenko Date: Sat, 3 Jun 2023 20:36:12 +0400 Subject: [PATCH 2/9] Fix Signed-off-by: Oleg Kulachenko --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2b67204b977..895c2288676 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,7 +31,7 @@ jobs: - name: Checkout neofs-testcases repository uses: actions/checkout@v3 with: - repository: vvarg-229/neofs-testcases + repository: vvarg229/neofs-testcases ref: 'tst-skip' path: neofs-testcases From cb5d4fb52d14bc6c6a760a8f7425dbc2526abd08 Mon Sep 17 00:00:00 2001 From: Oleg Kulachenko Date: Tue, 6 Jun 2023 13:27:28 +0400 Subject: [PATCH 3/9] Change to smoke to debug Signed-off-by: Oleg Kulachenko --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 895c2288676..cf941613acc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -212,7 +212,7 @@ jobs: timeout-minutes: 480 if: github.event_name != 'pull_request' run: | - source venv.local-pytest/bin/activate && pytest --alluredir=${GITHUB_WORKSPACE}/allure-results pytest_tests/testsuites + source venv.local-pytest/bin/activate && pytest -m "smoke" --alluredir=${GITHUB_WORKSPACE}/allure-results pytest_tests/testsuites working-directory: neofs-testcases ################################################################ From 822c493a84d13c416b4c765fdac346d45ba125fe Mon Sep 17 00:00:00 2001 From: Oleg Kulachenko Date: Sat, 10 Jun 2023 15:05:32 +0400 Subject: [PATCH 4/9] Remove smoke Signed-off-by: Oleg Kulachenko --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index cf941613acc..895c2288676 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -212,7 +212,7 @@ jobs: timeout-minutes: 480 if: github.event_name != 'pull_request' run: | - source venv.local-pytest/bin/activate && pytest -m "smoke" --alluredir=${GITHUB_WORKSPACE}/allure-results pytest_tests/testsuites + source venv.local-pytest/bin/activate && pytest --alluredir=${GITHUB_WORKSPACE}/allure-results pytest_tests/testsuites working-directory: neofs-testcases ################################################################ From 52c6ef006e1899a12d3025afe1f9abcd16dabcab Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 27 Jun 2023 21:20:38 +0400 Subject: [PATCH 5/9] sidechain/deploy: Use single blockchain monitoring utility Previously, Sidechain deployment procedure initialized and stopped multiple times. It's more efficient to run monitor once (it's almost always needed) and stop at the end of the procedure. This also prevents duplicated log messages about new block arrival. Signed-off-by: Leonard Lyubich --- pkg/morph/deploy/deploy.go | 10 ++++++++ pkg/morph/deploy/group.go | 21 ++++++++--------- pkg/morph/deploy/nns.go | 13 ++++------- pkg/morph/deploy/notary.go | 48 ++++++++++++++++++-------------------- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go index 20a55a35a93..bb22e93ec61 100644 --- a/pkg/morph/deploy/deploy.go +++ b/pkg/morph/deploy/deploy.go @@ -121,9 +121,17 @@ func Deploy(ctx context.Context, prm Prm) error { return errors.New("local account does not belong to any Neo committee member") } + monitor, err := newBlockchainMonitor(prm.Logger, prm.Blockchain) + if err != nil { + return fmt.Errorf("init blockchain monitor: %w", err) + } + + defer monitor.stop() + deployNNSPrm := deployNNSContractPrm{ logger: prm.Logger, blockchain: prm.Blockchain, + monitor: monitor, localAcc: prm.LocalAccount, localNEF: prm.NNS.Common.NEF, localManifest: prm.NNS.Common.Manifest, @@ -169,6 +177,7 @@ func Deploy(ctx context.Context, prm Prm) error { err = enableNotary(ctx, enableNotaryPrm{ logger: prm.Logger, blockchain: prm.Blockchain, + monitor: monitor, nnsOnChainAddress: nnsOnChainAddress, systemEmail: prm.NNS.SystemEmail, committee: committee, @@ -186,6 +195,7 @@ func Deploy(ctx context.Context, prm Prm) error { committeeGroupKey, err := initCommitteeGroup(ctx, initCommitteeGroupPrm{ logger: prm.Logger, blockchain: prm.Blockchain, + monitor: monitor, nnsOnChainAddress: nnsOnChainAddress, systemEmail: prm.NNS.SystemEmail, committee: committee, diff --git a/pkg/morph/deploy/group.go b/pkg/morph/deploy/group.go index f26e57b8ec7..69a6ca74126 100644 --- a/pkg/morph/deploy/group.go +++ b/pkg/morph/deploy/group.go @@ -24,6 +24,9 @@ type initCommitteeGroupPrm struct { blockchain Blockchain + // based on blockchain + monitor *blockchainMonitor + nnsOnChainAddress util.Uint160 systemEmail string @@ -36,19 +39,13 @@ type initCommitteeGroupPrm struct { // initCommitteeGroup initializes committee group and returns corresponding private key. func initCommitteeGroup(ctx context.Context, prm initCommitteeGroupPrm) (*keys.PrivateKey, error) { - monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) - if err != nil { - return nil, fmt.Errorf("init blockchain monitor: %w", err) - } - defer monitor.stop() - inv := invoker.New(prm.blockchain, nil) const leaderCommitteeIndex = 0 var committeeGroupKey *keys.PrivateKey var leaderTick func() upperLoop: - for ; ; monitor.waitForNextBlock(ctx) { + for ; ; prm.monitor.waitForNextBlock(ctx) { select { case <-ctx.Done(): return nil, fmt.Errorf("wait for committee group key to be distributed: %w", ctx.Err()) @@ -100,6 +97,8 @@ upperLoop: continue } + var err error + if committeeGroupKey == nil { committeeGroupKey, err = prm.keyStorage.GetPersistedPrivateKey() if err != nil { @@ -109,7 +108,7 @@ upperLoop: } if leaderTick == nil { - leaderTick, err = initShareCommitteeGroupKeyAsLeaderTick(prm, monitor, committeeGroupKey) + leaderTick, err = initShareCommitteeGroupKeyAsLeaderTick(prm, committeeGroupKey) if err != nil { prm.logger.Error("failed to construct action sharing committee group key between committee members as leader, will try again later", zap.Error(err)) @@ -124,7 +123,7 @@ upperLoop: // initShareCommitteeGroupKeyAsLeaderTick returns a function that preserves // context of the committee group key distribution by leading committee member // between calls. -func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, monitor *blockchainMonitor, committeeGroupKey *keys.PrivateKey) (func(), error) { +func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committeeGroupKey *keys.PrivateKey) (func(), error) { _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) @@ -153,7 +152,7 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, monitor * if ok && vubs[0] > 0 { l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= vubs[0] { + if cur := prm.monitor.currentHeight(); cur <= vubs[0] { l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[0])) return @@ -194,7 +193,7 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, monitor * if ok && vubs[1] > 0 { l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= vubs[1] { + if cur := prm.monitor.currentHeight(); cur <= vubs[1] { l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[1])) return diff --git a/pkg/morph/deploy/nns.go b/pkg/morph/deploy/nns.go index 109a0242cee..1b1ebf2cdaf 100644 --- a/pkg/morph/deploy/nns.go +++ b/pkg/morph/deploy/nns.go @@ -66,6 +66,9 @@ type deployNNSContractPrm struct { blockchain Blockchain + // based on blockchain + monitor *blockchainMonitor + localAcc *wallet.Account localNEF nef.File @@ -91,17 +94,11 @@ type deployNNSContractPrm struct { // If contract is missing and deployNNSContractPrm.initCommitteeGroupKey is provided, // initNNSContract attempts to deploy local contract. func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Uint160, err error) { - monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) - if err != nil { - return res, fmt.Errorf("init blockchain monitor: %w", err) - } - defer monitor.stop() - var managementContract *management.Contract var sentTxValidUntilBlock uint32 var committeeGroupKey *keys.PrivateKey - for ; ; monitor.waitForNextBlock(ctx) { + for ; ; prm.monitor.waitForNextBlock(ctx) { select { case <-ctx.Done(): return res, fmt.Errorf("wait for NNS contract synchronization: %w", ctx.Err()) @@ -149,7 +146,7 @@ func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Ui if sentTxValidUntilBlock > 0 { prm.logger.Info("transaction deploying NNS contract was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= sentTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= sentTxValidUntilBlock { prm.logger.Info("previously sent transaction deploying NNS contract may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) continue diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go index 799a705061d..470a79e4c6a 100644 --- a/pkg/morph/deploy/notary.go +++ b/pkg/morph/deploy/notary.go @@ -31,6 +31,9 @@ type enableNotaryPrm struct { blockchain Blockchain + // based on blockchain + monitor *blockchainMonitor + nnsOnChainAddress util.Uint160 systemEmail string @@ -41,18 +44,13 @@ type enableNotaryPrm struct { // enableNotary makes Notary service ready-to-go for the committee members. func enableNotary(ctx context.Context, prm enableNotaryPrm) error { - monitor, err := newBlockchainMonitor(prm.logger, prm.blockchain) - if err != nil { - return fmt.Errorf("init blockchain monitor: %w", err) - } - defer monitor.stop() - var tick func() + var err error if len(prm.committee) == 1 { prm.logger.Info("committee is single-acc, no multi-signature needed for Notary role designation") - tick, err = initDesignateNotaryRoleToLocalAccountTick(prm, monitor) + tick, err = initDesignateNotaryRoleToLocalAccountTick(prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the local account: %w", err) } @@ -60,12 +58,12 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { prm.logger.Info("committee is multi-acc, multi-signature is needed for Notary role designation") if prm.localAccCommitteeIndex == 0 { - tick, err = initDesignateNotaryRoleAsLeaderTick(prm, monitor) + tick, err = initDesignateNotaryRoleAsLeaderTick(prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the multi-acc committee as leader: %w", err) } } else { - tick, err = initDesignateNotaryRoleAsSignerTick(prm, monitor) + tick, err = initDesignateNotaryRoleAsSignerTick(prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the multi-acc committee as signer: %w", err) } @@ -74,7 +72,7 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { roleContract := rolemgmt.NewReader(invoker.New(prm.blockchain, nil)) - for ; ; monitor.waitForNextBlock(ctx) { + for ; ; prm.monitor.waitForNextBlock(ctx) { select { case <-ctx.Done(): return fmt.Errorf("wait for Notary service to be enabled for the committee: %w", ctx.Err()) @@ -83,7 +81,7 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { prm.logger.Info("checking Notary role of the committee members...") - accsWithNotaryRole, err := roleContract.GetDesignatedByRole(noderoles.P2PNotary, monitor.currentHeight()) + accsWithNotaryRole, err := roleContract.GetDesignatedByRole(noderoles.P2PNotary, prm.monitor.currentHeight()) if err != nil { prm.logger.Error("failed to check role of the committee, will try again later", zap.Error(err)) continue @@ -111,7 +109,7 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { // initDesignateNotaryRoleToLocalAccountTick returns a function that preserves // context of the Notary role designation to the local account between calls. -func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { +func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), error) { _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) @@ -123,7 +121,7 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm, monitor *blo var sentTxValidUntilBlock uint32 return func() { - if sentTxValidUntilBlock > 0 && sentTxValidUntilBlock <= monitor.currentHeight() { + if sentTxValidUntilBlock > 0 && sentTxValidUntilBlock <= prm.monitor.currentHeight() { prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome") return } @@ -131,7 +129,7 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm, monitor *blo if sentTxValidUntilBlock > 0 { prm.logger.Info("transaction designating Notary role to the local account was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= sentTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= sentTxValidUntilBlock { prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) return @@ -165,7 +163,7 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm, monitor *blo // of the Notary role designation to the multi-acc committee between calls. The // operation is performed by the leading committee member which is assigned to // collect signatures for the corresponding transaction. -func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { +func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) @@ -247,9 +245,9 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai var txValidUntilBlock uint32 if defaultValidUntilBlockIncrement <= ver.Protocol.MaxValidUntilBlockIncrement { - txValidUntilBlock = monitor.currentHeight() + defaultValidUntilBlockIncrement + txValidUntilBlock = prm.monitor.currentHeight() + defaultValidUntilBlockIncrement } else { - txValidUntilBlock = monitor.currentHeight() + ver.Protocol.MaxValidUntilBlockIncrement + txValidUntilBlock = prm.monitor.currentHeight() + ver.Protocol.MaxValidUntilBlockIncrement } strSharedTxData := sharedTransactionData{ @@ -291,7 +289,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai if registerDomainTxValidUntilBlock > 0 { l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) return @@ -329,7 +327,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai if setDomainRecordTxValidUntilBlock > 0 { l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) return @@ -349,7 +347,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai return } - if cur := monitor.currentHeight(); cur > sharedTxData.validUntilBlock { + if cur := prm.monitor.currentHeight(); cur > sharedTxData.validUntilBlock { l.Error("previously used shared data of the transaction expired, need a reset", zap.Uint32("expires after height", sharedTxData.validUntilBlock), zap.Uint32("current height", cur)) generateAndShareTxData(true) @@ -481,7 +479,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai if designateRoleTxValidUntilBlock > 0 { prm.logger.Info("transaction designating Notary role to the committee was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= designateRoleTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= designateRoleTxValidUntilBlock { prm.logger.Info("previously sent transaction designating Notary role to the committee may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", designateRoleTxValidUntilBlock)) return @@ -545,7 +543,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm, monitor *blockchai // of the Notary role designation to the multi-acc committee between calls. The // operation is performed by the non-leading committee member which is assigned to // sign transaction submitted by the leader. -func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm, monitor *blockchainMonitor) (func(), error) { +func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) @@ -621,7 +619,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm, monitor *blockchai return } - if cur := monitor.currentHeight(); cur > sharedTxData.validUntilBlock { + if cur := prm.monitor.currentHeight(); cur > sharedTxData.validUntilBlock { l.Error("previously used shared data of the transaction expired, will wait for update by leader", zap.Uint32("expires after height", sharedTxData.validUntilBlock), zap.Uint32("current height", cur)) resetTx() @@ -663,7 +661,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm, monitor *blockchai if registerDomainTxValidUntilBlock > 0 { l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) return @@ -701,7 +699,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm, monitor *blockchai if setDomainRecordTxValidUntilBlock > 0 { l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - if cur := monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { + if cur := prm.monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) return From 18e3f68106f4e9d5f5ecda8f4b1caef74358be55 Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Tue, 4 Jul 2023 22:44:07 +0300 Subject: [PATCH 6/9] go.mod: Update SDK to RC9 Signed-off-by: Pavel Karpy --- CHANGELOG.md | 1 + cmd/neofs-cli/internal/client/client.go | 116 +++++++++++------- cmd/neofs-cli/internal/client/sdk.go | 11 +- cmd/neofs-node/container.go | 6 +- cmd/neofs-node/object.go | 23 ++-- .../reputation/internal/client/client.go | 33 ++--- go.mod | 2 +- go.sum | 4 +- pkg/core/client/client.go | 24 ++-- pkg/innerring/internal/client/client.go | 43 +------ pkg/network/cache/multi.go | 66 +++++----- pkg/services/object/acl/acl.go | 4 +- pkg/services/object/get/v2/util.go | 3 +- pkg/services/object/internal/client/client.go | 56 +++------ pkg/services/policer/check.go | 3 +- pkg/services/tree/signature.go | 4 +- pkg/services/util/sign.go | 2 +- 17 files changed, 179 insertions(+), 222 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef825fcb30e..6a030b41a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Changelog for NeoFS Node - CLI default timeout for commands with `--await` flag increased to 1m (#2124) ### Updated +- `neofs-sdk-go` to `v1.0.0-rc.9` ### Updating from v0.37.0 CLI command timeouts (flag `--timeout`) now limit the total command execution diff --git a/cmd/neofs-cli/internal/client/client.go b/cmd/neofs-cli/internal/client/client.go index ad48d300c08..4ba1da6d718 100644 --- a/cmd/neofs-cli/internal/client/client.go +++ b/cmd/neofs-cli/internal/client/client.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -26,12 +27,12 @@ type BalanceOfPrm struct { // BalanceOfRes groups the resulting values of BalanceOf operation. type BalanceOfRes struct { - cliRes *client.ResBalanceGet + cliRes accounting.Decimal } // Balance returns the current balance. func (x BalanceOfRes) Balance() accounting.Decimal { - return x.cliRes.Amount() + return x.cliRes } // BalanceOf requests the current balance of a NeoFS user. @@ -46,24 +47,31 @@ func BalanceOf(ctx context.Context, prm BalanceOfPrm) (res BalanceOfRes, err err // ListContainersPrm groups parameters of ListContainers operation. type ListContainersPrm struct { commonPrm + + owner user.ID client.PrmContainerList } +// SetAccount sets containers' owner. +func (l *ListContainersPrm) SetAccount(owner user.ID) { + l.owner = owner +} + // ListContainersRes groups the resulting values of ListContainers operation. type ListContainersRes struct { - cliRes *client.ResContainerList + cliRes []cid.ID } // IDList returns list of identifiers of user's containers. func (x ListContainersRes) IDList() []cid.ID { - return x.cliRes.Containers() + return x.cliRes } // ListContainers requests a list of NeoFS user's containers. // // Returns any error which prevented the operation from completing correctly in error return. func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContainersRes, err error) { - res.cliRes, err = prm.cli.ContainerList(ctx, prm.PrmContainerList) + res.cliRes, err = prm.cli.ContainerList(ctx, prm.owner, prm.PrmContainerList) return } @@ -71,9 +79,16 @@ func ListContainers(ctx context.Context, prm ListContainersPrm) (res ListContain // PutContainerPrm groups parameters of PutContainer operation. type PutContainerPrm struct { commonPrm + + cnr containerSDK.Container client.PrmContainerPut } +// SetContainer sets container. +func (p *PutContainerPrm) SetContainer(cnr containerSDK.Container) { + p.cnr = cnr +} + // PutContainerRes groups the resulting values of PutContainer operation. type PutContainerRes struct { cnr cid.ID @@ -93,9 +108,9 @@ func (x PutContainerRes) ID() cid.ID { // // Returns any error which prevented the operation from completing correctly in error return. func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes, err error) { - cliRes, err := prm.cli.ContainerPut(ctx, prm.PrmContainerPut) + cliRes, err := prm.cli.ContainerPut(ctx, prm.cnr, prm.PrmContainerPut) if err == nil { - res.cnr = cliRes.ID() + res.cnr = cliRes } return @@ -104,29 +119,31 @@ func PutContainer(ctx context.Context, prm PutContainerPrm) (res PutContainerRes // GetContainerPrm groups parameters of GetContainer operation. type GetContainerPrm struct { commonPrm + + cid cid.ID cliPrm client.PrmContainerGet } // SetContainer sets identifier of the container to be read. func (x *GetContainerPrm) SetContainer(id cid.ID) { - x.cliPrm.SetContainer(id) + x.cid = id } // GetContainerRes groups the resulting values of GetContainer operation. type GetContainerRes struct { - cliRes *client.ResContainerGet + cliRes containerSDK.Container } // Container returns structured of the requested container. func (x GetContainerRes) Container() containerSDK.Container { - return x.cliRes.Container() + return x.cliRes } // GetContainer reads a container from NeoFS by ID. // // Returns any error which prevented the operation from completing correctly in error return. func GetContainer(ctx context.Context, prm GetContainerPrm) (res GetContainerRes, err error) { - res.cliRes, err = prm.cli.ContainerGet(ctx, prm.cliPrm) + res.cliRes, err = prm.cli.ContainerGet(ctx, prm.cid, prm.cliPrm) return } @@ -149,9 +166,16 @@ func IsACLExtendable(ctx context.Context, c *client.Client, cnr cid.ID) (bool, e // DeleteContainerPrm groups parameters of DeleteContainerPrm operation. type DeleteContainerPrm struct { commonPrm + + cid cid.ID client.PrmContainerDelete } +// SetContainer sets an ID of a container to be removed. +func (d *DeleteContainerPrm) SetContainer(cid cid.ID) { + d.cid = cid +} + // DeleteContainerRes groups the resulting values of DeleteContainer operation. type DeleteContainerRes struct{} @@ -164,7 +188,7 @@ type DeleteContainerRes struct{} // // Returns any error which prevented the operation from completing correctly in error return. func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteContainerRes, err error) { - _, err = prm.cli.ContainerDelete(ctx, prm.PrmContainerDelete) + err = prm.cli.ContainerDelete(ctx, prm.cid, prm.PrmContainerDelete) return } @@ -172,24 +196,32 @@ func DeleteContainer(ctx context.Context, prm DeleteContainerPrm) (res DeleteCon // EACLPrm groups parameters of EACL operation. type EACLPrm struct { commonPrm + + cid cid.ID client.PrmContainerEACL } +// SetContainer sets container ID to be requested +// for its eACL. +func (E *EACLPrm) SetContainer(cid cid.ID) { + E.cid = cid +} + // EACLRes groups the resulting values of EACL operation. type EACLRes struct { - cliRes *client.ResContainerEACL + cliRes eacl.Table } // EACL returns requested eACL table. func (x EACLRes) EACL() eacl.Table { - return x.cliRes.Table() + return x.cliRes } // EACL reads eACL table from NeoFS by container ID. // // Returns any error which prevented the operation from completing correctly in error return. func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) { - res.cliRes, err = prm.cli.ContainerEACL(ctx, prm.PrmContainerEACL) + res.cliRes, err = prm.cli.ContainerEACL(ctx, prm.cid, prm.PrmContainerEACL) return } @@ -197,9 +229,16 @@ func EACL(ctx context.Context, prm EACLPrm) (res EACLRes, err error) { // SetEACLPrm groups parameters of SetEACL operation. type SetEACLPrm struct { commonPrm + + table eacl.Table client.PrmContainerSetEACL } +// SetTable sets extended Access Control List table to be applied. +func (s *SetEACLPrm) SetTable(table eacl.Table) { + s.table = table +} + // SetEACLRes groups the resulting values of SetEACL operation. type SetEACLRes struct{} @@ -212,7 +251,7 @@ type SetEACLRes struct{} // // Returns any error which prevented the operation from completing correctly in error return. func SetEACL(ctx context.Context, prm SetEACLPrm) (res SetEACLRes, err error) { - _, err = prm.cli.ContainerSetEACL(ctx, prm.PrmContainerSetEACL) + err = prm.cli.ContainerSetEACL(ctx, prm.table, prm.PrmContainerSetEACL) return } @@ -225,12 +264,12 @@ type NetworkInfoPrm struct { // NetworkInfoRes groups the resulting values of NetworkInfo operation. type NetworkInfoRes struct { - cliRes *client.ResNetworkInfo + cliRes netmap.NetworkInfo } // NetworkInfo returns structured information about the NeoFS network. func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo { - return x.cliRes.Info() + return x.cliRes } // NetworkInfo reads information about the NeoFS network. @@ -279,12 +318,12 @@ type NetMapSnapshotPrm struct { // NetMapSnapshotRes groups the resulting values of NetMapSnapshot operation. type NetMapSnapshotRes struct { - cliRes *client.ResNetMapSnapshot + cliRes netmap.NetMap } // NetMap returns current local snapshot of the NeoFS network map. func (x NetMapSnapshotRes) NetMap() netmap.NetMap { - return x.cliRes.NetMap() + return x.cliRes } // NetMapSnapshot requests current network view of the remote server. @@ -464,8 +503,6 @@ func (x DeleteObjectRes) Tombstone() oid.ID { // Returns any error which prevented the operation from completing correctly in error return. func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, error) { var delPrm client.PrmObjectDelete - delPrm.FromContainer(prm.objAddr.Container()) - delPrm.ByID(prm.objAddr.Object()) if prm.sessionToken != nil { delPrm.WithinSession(*prm.sessionToken) @@ -477,13 +514,13 @@ func DeleteObject(ctx context.Context, prm DeleteObjectPrm) (*DeleteObjectRes, e delPrm.WithXHeaders(prm.xHeaders...) - cliRes, err := prm.cli.ObjectDelete(ctx, delPrm) + cliRes, err := prm.cli.ObjectDelete(ctx, prm.objAddr.Container(), prm.objAddr.Object(), delPrm) if err != nil { return nil, fmt.Errorf("remove object via client: %w", err) } return &DeleteObjectRes{ - tomb: cliRes.Tombstone(), + tomb: cliRes, }, nil } @@ -520,8 +557,6 @@ func (x GetObjectRes) Header() *object.Object { // For raw reading, returns *object.SplitInfoError error if object is virtual. func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) { var getPrm client.PrmObjectGet - getPrm.FromContainer(prm.objAddr.Container()) - getPrm.ByID(prm.objAddr.Object()) if prm.sessionToken != nil { getPrm.WithinSession(*prm.sessionToken) @@ -541,7 +576,7 @@ func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) { getPrm.WithXHeaders(prm.xHeaders...) - rdr, err := prm.cli.ObjectGetInit(ctx, getPrm) + rdr, err := prm.cli.ObjectGetInit(ctx, prm.objAddr.Container(), prm.objAddr.Object(), getPrm) if err != nil { return nil, fmt.Errorf("init object reading on client: %w", err) } @@ -549,8 +584,7 @@ func GetObject(ctx context.Context, prm GetObjectPrm) (*GetObjectRes, error) { var hdr object.Object if !rdr.ReadHeader(&hdr) { - _, err = rdr.Close() - return nil, fmt.Errorf("read object header: %w", err) + return nil, fmt.Errorf("read object header: %w", rdr.Close()) } if prm.headerCallback != nil { prm.headerCallback(&hdr) @@ -596,8 +630,6 @@ func (x HeadObjectRes) Header() *object.Object { // For raw reading, returns *object.SplitInfoError error if object is virtual. func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) { var cliPrm client.PrmObjectHead - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) if prm.sessionToken != nil { cliPrm.WithinSession(*prm.sessionToken) @@ -617,7 +649,7 @@ func HeadObject(ctx context.Context, prm HeadObjectPrm) (*HeadObjectRes, error) cliPrm.WithXHeaders(prm.xHeaders...) - res, err := prm.cli.ObjectHead(ctx, cliPrm) + res, err := prm.cli.ObjectHead(ctx, prm.objAddr.Container(), prm.objAddr.Object(), cliPrm) if err != nil { return nil, fmt.Errorf("read object header via client: %w", err) } @@ -661,7 +693,6 @@ func (x SearchObjectsRes) IDList() []oid.ID { // Returns any error which prevented the operation from completing correctly in error return. func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes, error) { var cliPrm client.PrmObjectSearch - cliPrm.InContainer(prm.cnrID) cliPrm.SetFilters(prm.filters) if prm.sessionToken != nil { @@ -678,7 +709,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes cliPrm.WithXHeaders(prm.xHeaders...) - rdr, err := prm.cli.ObjectSearchInit(ctx, cliPrm) + rdr, err := prm.cli.ObjectSearchInit(ctx, prm.cnrID, cliPrm) if err != nil { return nil, fmt.Errorf("init object search: %w", err) } @@ -698,7 +729,7 @@ func SearchObjects(ctx context.Context, prm SearchObjectsPrm) (*SearchObjectsRes } } - _, err = rdr.Close() + err = rdr.Close() if err != nil { return nil, fmt.Errorf("read object list: %w", err) } @@ -737,12 +768,12 @@ func (x *HashPayloadRangesPrm) SetSalt(salt []byte) { // HashPayloadRangesRes groups the resulting values of HashPayloadRanges operation. type HashPayloadRangesRes struct { - cliRes *client.ResObjectHash + cliRes [][]byte } // HashList returns a list of hashes of the payload ranges keeping order. func (x HashPayloadRangesRes) HashList() [][]byte { - return x.cliRes.Checksums() + return x.cliRes } // HashPayloadRanges requests hashes (by default SHA256) of the object payload ranges. @@ -751,8 +782,6 @@ func (x HashPayloadRangesRes) HashList() [][]byte { // Returns an error if number of received hashes differs with the number of requested ranges. func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) { var cliPrm client.PrmObjectHash - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) if prm.local { cliPrm.MarkLocal() @@ -783,7 +812,7 @@ func HashPayloadRanges(ctx context.Context, prm HashPayloadRangesPrm) (*HashPayl cliPrm.WithXHeaders(prm.xHeaders...) - res, err := prm.cli.ObjectHash(ctx, cliPrm) + res, err := prm.cli.ObjectHash(ctx, prm.objAddr.Container(), prm.objAddr.Object(), cliPrm) if err != nil { return nil, fmt.Errorf("read payload hashes via client: %w", err) } @@ -819,8 +848,6 @@ type PayloadRangeRes struct{} // For raw reading, returns *object.SplitInfoError error if object is virtual. func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, error) { var cliPrm client.PrmObjectRange - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) if prm.sessionToken != nil { cliPrm.WithinSession(*prm.sessionToken) @@ -838,12 +865,9 @@ func PayloadRange(ctx context.Context, prm PayloadRangePrm) (*PayloadRangeRes, e cliPrm.MarkLocal() } - cliPrm.SetOffset(prm.rng.GetOffset()) - cliPrm.SetLength(prm.rng.GetLength()) - cliPrm.WithXHeaders(prm.xHeaders...) - rdr, err := prm.cli.ObjectRangeInit(ctx, cliPrm) + rdr, err := prm.cli.ObjectRangeInit(ctx, prm.objAddr.Container(), prm.objAddr.Object(), prm.rng.GetOffset(), prm.rng.GetLength(), cliPrm) if err != nil { return nil, fmt.Errorf("init payload reading: %w", err) } diff --git a/cmd/neofs-cli/internal/client/sdk.go b/cmd/neofs-cli/internal/client/sdk.go index 0258d48141f..b603bfa8c1a 100644 --- a/cmd/neofs-cli/internal/client/sdk.go +++ b/cmd/neofs-cli/internal/client/sdk.go @@ -42,13 +42,11 @@ func getSDKClientByFlag(ctx context.Context, cmd *cobra.Command, key *ecdsa.Priv // GetSDKClient returns default neofs-sdk-go client. func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) { var ( - c client.Client prmInit client.PrmInit prmDial client.PrmDial ) prmInit.SetDefaultSigner(neofsecdsa.SignerRFC6979(*key)) - prmInit.ResolveNeoFSFailures() prmDial.SetServerURI(addr.URIAddr()) prmDial.SetContext(ctx) @@ -63,13 +61,16 @@ func GetSDKClient(ctx context.Context, cmd *cobra.Command, key *ecdsa.PrivateKey } } - c.Init(prmInit) + c, err := client.New(prmInit) + if err != nil { + return nil, fmt.Errorf("can't create SDK client: %w", err) + } if err := c.Dial(prmDial); err != nil { //nolint:contextcheck // SetContext is used above. return nil, fmt.Errorf("can't init SDK client: %w", err) } - return &c, nil + return c, nil } // GetCurrentEpoch returns current epoch. @@ -95,5 +96,5 @@ func GetCurrentEpoch(ctx context.Context, cmd *cobra.Command, endpoint string) ( return 0, err } - return ni.Info().CurrentEpoch(), nil + return ni.CurrentEpoch(), nil } diff --git a/cmd/neofs-node/container.go b/cmd/neofs-node/container.go index 55a5fa415f1..5bc8129c9b7 100644 --- a/cmd/neofs-node/container.go +++ b/cmd/neofs-node/container.go @@ -379,11 +379,7 @@ func (r *remoteLoadAnnounceWriter) Put(a containerSDK.SizeEstimation) error { func (r *remoteLoadAnnounceWriter) Close() error { var cliPrm apiClient.PrmAnnounceSpace - - cliPrm.SetValues(r.buf) - - _, err := r.client.ContainerAnnounceUsedSpace(r.ctx, cliPrm) - return err + return r.client.ContainerAnnounceUsedSpace(r.ctx, r.buf, cliPrm) } type loadPlacementBuilder struct { diff --git a/cmd/neofs-node/object.go b/cmd/neofs-node/object.go index ed6fb15ee8f..3ef10836006 100644 --- a/cmd/neofs-node/object.go +++ b/cmd/neofs-node/object.go @@ -37,7 +37,6 @@ import ( truststorage "github.com/nspcc-dev/neofs-node/pkg/services/reputation/local/storage" "github.com/nspcc-dev/neofs-node/pkg/util/logger" "github.com/nspcc-dev/neofs-sdk-go/client" - apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl" objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" @@ -443,19 +442,17 @@ func (c *reputationClient) ObjectPutInit(ctx context.Context, prm client.PrmObje return res, err } -func (c *reputationClient) ObjectDelete(ctx context.Context, prm client.PrmObjectDelete) (*client.ResObjectDelete, error) { - res, err := c.MultiAddressClient.ObjectDelete(ctx, prm) +func (c *reputationClient) ObjectDelete(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectDelete) (oid.ID, error) { + res, err := c.MultiAddressClient.ObjectDelete(ctx, containerID, objectID, prm) if err != nil { c.submitResult(err) - } else { - c.submitResult(apistatus.ErrFromStatus(res.Status())) } return res, err } -func (c *reputationClient) GetObjectInit(ctx context.Context, prm client.PrmObjectGet) (*client.ObjectReader, error) { - res, err := c.MultiAddressClient.ObjectGetInit(ctx, prm) +func (c *reputationClient) GetObjectInit(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectGet) (*client.ObjectReader, error) { + res, err := c.MultiAddressClient.ObjectGetInit(ctx, containerID, objectID, prm) // FIXME: (neofs-node#1193) here we submit only initialization errors, reading errors are not processed c.submitResult(err) @@ -463,24 +460,24 @@ func (c *reputationClient) GetObjectInit(ctx context.Context, prm client.PrmObje return res, err } -func (c *reputationClient) ObjectHead(ctx context.Context, prm client.PrmObjectHead) (*client.ResObjectHead, error) { - res, err := c.MultiAddressClient.ObjectHead(ctx, prm) +func (c *reputationClient) ObjectHead(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHead) (*client.ResObjectHead, error) { + res, err := c.MultiAddressClient.ObjectHead(ctx, containerID, objectID, prm) c.submitResult(err) return res, err } -func (c *reputationClient) ObjectHash(ctx context.Context, prm client.PrmObjectHash) (*client.ResObjectHash, error) { - res, err := c.MultiAddressClient.ObjectHash(ctx, prm) +func (c *reputationClient) ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHash) ([][]byte, error) { + res, err := c.MultiAddressClient.ObjectHash(ctx, containerID, objectID, prm) c.submitResult(err) return res, err } -func (c *reputationClient) ObjectSearchInit(ctx context.Context, prm client.PrmObjectSearch) (*client.ObjectListReader, error) { - res, err := c.MultiAddressClient.ObjectSearchInit(ctx, prm) +func (c *reputationClient) ObjectSearchInit(ctx context.Context, containerID cid.ID, prm client.PrmObjectSearch) (*client.ObjectListReader, error) { + res, err := c.MultiAddressClient.ObjectSearchInit(ctx, containerID, prm) // FIXME: (neofs-node#1193) here we submit only initialization errors, reading errors are not processed c.submitResult(err) diff --git a/cmd/neofs-node/reputation/internal/client/client.go b/cmd/neofs-node/reputation/internal/client/client.go index 6f845ce2dc5..e3e0ce2b515 100644 --- a/cmd/neofs-node/reputation/internal/client/client.go +++ b/cmd/neofs-node/reputation/internal/client/client.go @@ -5,7 +5,6 @@ import ( coreclient "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-sdk-go/client" - apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/reputation" ) @@ -33,17 +32,19 @@ func (x *commonPrm) SetContext(ctx context.Context) { type AnnounceLocalPrm struct { commonPrm + epoch uint64 + trusts []reputation.Trust cliPrm client.PrmAnnounceLocalTrust } // SetEpoch sets the epoch in which the trust was assessed. func (x *AnnounceLocalPrm) SetEpoch(epoch uint64) { - x.cliPrm.SetEpoch(epoch) + x.epoch = epoch } // SetTrusts sets a list of local trust values. func (x *AnnounceLocalPrm) SetTrusts(ts []reputation.Trust) { - x.cliPrm.SetValues(ts) + x.trusts = ts } // AnnounceLocalRes groups the resulting values of AnnounceLocal operation. @@ -55,14 +56,7 @@ type AnnounceLocalRes struct{} // // Returns any error which prevented the operation from completing correctly in error return. func AnnounceLocal(prm AnnounceLocalPrm) (res AnnounceLocalRes, err error) { - var cliRes *client.ResAnnounceLocalTrust - - cliRes, err = prm.cli.AnnounceLocalTrust(prm.ctx, prm.cliPrm) - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(cliRes.Status()) - } - + err = prm.cli.AnnounceLocalTrust(prm.ctx, prm.epoch, prm.trusts, prm.cliPrm) return } @@ -70,12 +64,14 @@ func AnnounceLocal(prm AnnounceLocalPrm) (res AnnounceLocalRes, err error) { type AnnounceIntermediatePrm struct { commonPrm - cliPrm client.PrmAnnounceIntermediateTrust + epoch uint64 + p2pTrust reputation.PeerToPeerTrust + cliPrm client.PrmAnnounceIntermediateTrust } // SetEpoch sets the number of the epoch when the trust calculation's iteration was executed. func (x *AnnounceIntermediatePrm) SetEpoch(epoch uint64) { - x.cliPrm.SetEpoch(epoch) + x.epoch = epoch } // SetIteration sets the number of the iteration of the trust calculation algorithm. @@ -85,7 +81,7 @@ func (x *AnnounceIntermediatePrm) SetIteration(iter uint32) { // SetTrust sets the current global trust value computed at the iteration. func (x *AnnounceIntermediatePrm) SetTrust(t reputation.PeerToPeerTrust) { - x.cliPrm.SetCurrentValue(t) + x.p2pTrust = t } // AnnounceIntermediateRes groups the resulting values of AnnounceIntermediate operation. @@ -98,13 +94,6 @@ type AnnounceIntermediateRes struct{} // // Returns any error which prevented the operation from completing correctly in error return. func AnnounceIntermediate(prm AnnounceIntermediatePrm) (res AnnounceIntermediateRes, err error) { - var cliRes *client.ResAnnounceIntermediateTrust - - cliRes, err = prm.cli.AnnounceIntermediateTrust(prm.ctx, prm.cliPrm) - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(cliRes.Status()) - } - + err = prm.cli.AnnounceIntermediateTrust(prm.ctx, prm.epoch, prm.p2pTrust, prm.cliPrm) return } diff --git a/go.mod b/go.mod index 3f26cad57cb..cdf7e4f7d6a 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/nspcc-dev/neo-go v0.101.1 github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 github.com/nspcc-dev/neofs-contract v0.16.0 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.8 + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.9 github.com/nspcc-dev/tzhash v1.7.0 github.com/olekukonko/tablewriter v0.0.5 github.com/panjf2000/ants/v2 v2.4.0 diff --git a/go.sum b/go.sum index f1b4b142c48..938fe6c2685 100644 --- a/go.sum +++ b/go.sum @@ -366,8 +366,8 @@ github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09f github.com/nspcc-dev/neofs-crypto v0.4.0/go.mod h1:6XJ8kbXgOfevbI2WMruOtI+qUJXNwSGM/E9eClXxPHs= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.8 h1:bsg3o7Oiae2xHYAs1M5yg8GDOs46x/IW5jCh/4dt8uo= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.8/go.mod h1:kq/KoRhj/Ye8b7ctykiXej42Kq09lUg2E5FXGCbLOWs= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.9 h1:uIQlWUUo5n/e8rLFGm14zIValcpXU1HWuwaoXUAHt5Q= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.9/go.mod h1:fTsdTU/M9rvv/f9jlp7vHOm3DRp+NSfjfTv9NohrKTE= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= diff --git a/pkg/core/client/client.go b/pkg/core/client/client.go index fe435604a32..074da14f480 100644 --- a/pkg/core/client/client.go +++ b/pkg/core/client/client.go @@ -6,21 +6,25 @@ import ( rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/nspcc-dev/neofs-sdk-go/client" + "github.com/nspcc-dev/neofs-sdk-go/container" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + reputationSDK "github.com/nspcc-dev/neofs-sdk-go/reputation" ) // Client is an interface of NeoFS storage // node's client. type Client interface { - ContainerAnnounceUsedSpace(context.Context, client.PrmAnnounceSpace) (*client.ResAnnounceSpace, error) - ObjectPutInit(context.Context, client.PrmObjectPutInit) (*client.ObjectWriter, error) - ObjectDelete(context.Context, client.PrmObjectDelete) (*client.ResObjectDelete, error) - ObjectGetInit(context.Context, client.PrmObjectGet) (*client.ObjectReader, error) - ObjectHead(context.Context, client.PrmObjectHead) (*client.ResObjectHead, error) - ObjectSearchInit(context.Context, client.PrmObjectSearch) (*client.ObjectListReader, error) - ObjectRangeInit(context.Context, client.PrmObjectRange) (*client.ObjectRangeReader, error) - ObjectHash(context.Context, client.PrmObjectHash) (*client.ResObjectHash, error) - AnnounceLocalTrust(context.Context, client.PrmAnnounceLocalTrust) (*client.ResAnnounceLocalTrust, error) - AnnounceIntermediateTrust(context.Context, client.PrmAnnounceIntermediateTrust) (*client.ResAnnounceIntermediateTrust, error) + ContainerAnnounceUsedSpace(ctx context.Context, announcements []container.SizeEstimation, prm client.PrmAnnounceSpace) error + ObjectPutInit(ctx context.Context, prm client.PrmObjectPutInit) (*client.ObjectWriter, error) + ObjectDelete(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectDelete) (oid.ID, error) + ObjectGetInit(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectGet) (*client.ObjectReader, error) + ObjectHead(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHead) (*client.ResObjectHead, error) + ObjectSearchInit(ctx context.Context, containerID cid.ID, prm client.PrmObjectSearch) (*client.ObjectListReader, error) + ObjectRangeInit(ctx context.Context, containerID cid.ID, objectID oid.ID, offset, length uint64, prm client.PrmObjectRange) (*client.ObjectRangeReader, error) + ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHash) ([][]byte, error) + AnnounceLocalTrust(ctx context.Context, epoch uint64, trusts []reputationSDK.Trust, prm client.PrmAnnounceLocalTrust) error + AnnounceIntermediateTrust(ctx context.Context, epoch uint64, trust reputationSDK.PeerToPeerTrust, prm client.PrmAnnounceIntermediateTrust) error ExecRaw(f func(client *rawclient.Client) error) error Close() error } diff --git a/pkg/innerring/internal/client/client.go b/pkg/innerring/internal/client/client.go index fdbff31a335..70b561af1a5 100644 --- a/pkg/innerring/internal/client/client.go +++ b/pkg/innerring/internal/client/client.go @@ -10,7 +10,6 @@ import ( clientcore "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/storagegroup" "github.com/nspcc-dev/neofs-sdk-go/client" - apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -63,11 +62,10 @@ var sgFilter = storagegroup.SearchQuery() // Returns any error which prevented the operation from completing correctly in error return. func (x Client) SearchSG(prm SearchSGPrm) (*SearchSGRes, error) { var cliPrm client.PrmObjectSearch - cliPrm.InContainer(prm.cnrID) cliPrm.SetFilters(sgFilter) cliPrm.UseSigner(neofsecdsa.SignerRFC6979(*x.key)) - rdr, err := x.c.ObjectSearchInit(prm.ctx, cliPrm) + rdr, err := x.c.ObjectSearchInit(prm.ctx, prm.cnrID, cliPrm) if err != nil { return nil, fmt.Errorf("init object search: %w", err) } @@ -87,12 +85,7 @@ func (x Client) SearchSG(prm SearchSGPrm) (*SearchSGRes, error) { } } - res, err := rdr.Close() - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(res.Status()) - } - + err = rdr.Close() if err != nil { return nil, fmt.Errorf("read object list: %w", err) } @@ -122,11 +115,9 @@ func (x GetObjectRes) Object() *object.Object { // Returns any error which prevented the operation from completing correctly in error return. func (x Client) GetObject(prm GetObjectPrm) (*GetObjectRes, error) { var cliPrm client.PrmObjectGet - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) cliPrm.UseSigner(neofsecdsa.SignerRFC6979(*x.key)) - rdr, err := x.c.ObjectGetInit(prm.ctx, cliPrm) + rdr, err := x.c.ObjectGetInit(prm.ctx, prm.objAddr.Container(), prm.objAddr.Object(), cliPrm) if err != nil { return nil, fmt.Errorf("init object search: %w", err) } @@ -134,13 +125,7 @@ func (x Client) GetObject(prm GetObjectPrm) (*GetObjectRes, error) { var obj object.Object if !rdr.ReadHeader(&obj) { - res, err := rdr.Close() - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(res.Status()) - } - - return nil, fmt.Errorf("read object header: %w", err) + return nil, fmt.Errorf("read object header: %w", rdr.Close()) } buf := make([]byte, obj.PayloadSize()) @@ -201,16 +186,9 @@ func (x Client) HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) { cliPrm.MarkLocal() } - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) cliPrm.UseSigner(neofsecdsa.SignerRFC6979(*x.key)) - cliRes, err := x.c.ObjectHead(prm.ctx, cliPrm) - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(cliRes.Status()) - } - + cliRes, err := x.c.ObjectHead(prm.ctx, prm.objAddr.Container(), prm.objAddr.Object(), cliPrm) if err != nil { return nil, fmt.Errorf("read object header from NeoFS: %w", err) } @@ -301,20 +279,11 @@ func (x HashPayloadRangeRes) Hash() []byte { // Returns any error which prevented the operation from completing correctly in error return. func (x Client) HashPayloadRange(prm HashPayloadRangePrm) (res HashPayloadRangeRes, err error) { var cliPrm client.PrmObjectHash - cliPrm.FromContainer(prm.objAddr.Container()) - cliPrm.ByID(prm.objAddr.Object()) cliPrm.SetRangeList(prm.rng.GetOffset(), prm.rng.GetLength()) cliPrm.TillichZemorAlgo() - cliRes, err := x.c.ObjectHash(prm.ctx, cliPrm) + hs, err := x.c.ObjectHash(prm.ctx, prm.objAddr.Container(), prm.objAddr.Object(), cliPrm) if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(cliRes.Status()) - if err != nil { - return - } - - hs := cliRes.Checksums() if ln := len(hs); ln != 1 { err = fmt.Errorf("wrong number of checksums %d", ln) } else { diff --git a/pkg/network/cache/multi.go b/pkg/network/cache/multi.go index aabbc23ff3b..6ab4d17ce7e 100644 --- a/pkg/network/cache/multi.go +++ b/pkg/network/cache/multi.go @@ -11,8 +11,12 @@ import ( clientcore "github.com/nspcc-dev/neofs-node/pkg/core/client" "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/nspcc-dev/neofs-sdk-go/client" + "github.com/nspcc-dev/neofs-sdk-go/container" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + reputationSDK "github.com/nspcc-dev/neofs-sdk-go/reputation" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -50,7 +54,6 @@ func newMultiClient(addr network.AddressGroup, opts ClientCacheOpts) *multiClien func (x *multiClient) createForAddress(addr network.Address) (clientcore.Client, error) { var ( - c client.Client prmInit client.PrmInit prmDial client.PrmDial ) @@ -73,13 +76,17 @@ func (x *multiClient) createForAddress(addr network.Address) (clientcore.Client, prmInit.SetResponseInfoCallback(x.opts.ResponseCallback) } - c.Init(prmInit) - err := c.Dial(prmDial) + c, err := client.New(prmInit) + if err != nil { + return nil, fmt.Errorf("can't create SDK client: %w", err) + } + + err = c.Dial(prmDial) if err != nil { return nil, fmt.Errorf("can't init SDK client: %w", err) } - return &c, nil + return c, nil } // updateGroup replaces current multiClient addresses with a new group. @@ -215,85 +222,76 @@ func (x *multiClient) ObjectPutInit(ctx context.Context, p client.PrmObjectPutIn return } -func (x *multiClient) ContainerAnnounceUsedSpace(ctx context.Context, prm client.PrmAnnounceSpace) (res *client.ResAnnounceSpace, err error) { - err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ContainerAnnounceUsedSpace(ctx, prm) - return err +func (x *multiClient) ContainerAnnounceUsedSpace(ctx context.Context, announcements []container.SizeEstimation, prm client.PrmAnnounceSpace) error { + return x.iterateClients(ctx, func(c clientcore.Client) error { + return c.ContainerAnnounceUsedSpace(ctx, announcements, prm) }) - - return } -func (x *multiClient) ObjectDelete(ctx context.Context, p client.PrmObjectDelete) (res *client.ResObjectDelete, err error) { +func (x *multiClient) ObjectDelete(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectDelete) (tombID oid.ID, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectDelete(ctx, p) + tombID, err = c.ObjectDelete(ctx, containerID, objectID, prm) return err }) return } -func (x *multiClient) ObjectGetInit(ctx context.Context, p client.PrmObjectGet) (res *client.ObjectReader, err error) { +func (x *multiClient) ObjectGetInit(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectGet) (res *client.ObjectReader, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectGetInit(ctx, p) + res, err = c.ObjectGetInit(ctx, containerID, objectID, prm) return err }) return } -func (x *multiClient) ObjectRangeInit(ctx context.Context, p client.PrmObjectRange) (res *client.ObjectRangeReader, err error) { +func (x *multiClient) ObjectRangeInit(ctx context.Context, containerID cid.ID, objectID oid.ID, offset, length uint64, prm client.PrmObjectRange) (res *client.ObjectRangeReader, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectRangeInit(ctx, p) + res, err = c.ObjectRangeInit(ctx, containerID, objectID, offset, length, prm) return err }) return } -func (x *multiClient) ObjectHead(ctx context.Context, p client.PrmObjectHead) (res *client.ResObjectHead, err error) { +func (x *multiClient) ObjectHead(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHead) (res *client.ResObjectHead, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectHead(ctx, p) + res, err = c.ObjectHead(ctx, containerID, objectID, prm) return err }) return } -func (x *multiClient) ObjectHash(ctx context.Context, p client.PrmObjectHash) (res *client.ResObjectHash, err error) { +func (x *multiClient) ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, prm client.PrmObjectHash) (res [][]byte, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectHash(ctx, p) + res, err = c.ObjectHash(ctx, containerID, objectID, prm) return err }) return } -func (x *multiClient) ObjectSearchInit(ctx context.Context, p client.PrmObjectSearch) (res *client.ObjectListReader, err error) { +func (x *multiClient) ObjectSearchInit(ctx context.Context, containerID cid.ID, prm client.PrmObjectSearch) (res *client.ObjectListReader, err error) { err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.ObjectSearchInit(ctx, p) + res, err = c.ObjectSearchInit(ctx, containerID, prm) return err }) return } -func (x *multiClient) AnnounceLocalTrust(ctx context.Context, prm client.PrmAnnounceLocalTrust) (res *client.ResAnnounceLocalTrust, err error) { - err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.AnnounceLocalTrust(ctx, prm) - return err +func (x *multiClient) AnnounceLocalTrust(ctx context.Context, epoch uint64, trusts []reputationSDK.Trust, prm client.PrmAnnounceLocalTrust) error { + return x.iterateClients(ctx, func(c clientcore.Client) error { + return c.AnnounceLocalTrust(ctx, epoch, trusts, prm) }) - - return } -func (x *multiClient) AnnounceIntermediateTrust(ctx context.Context, prm client.PrmAnnounceIntermediateTrust) (res *client.ResAnnounceIntermediateTrust, err error) { - err = x.iterateClients(ctx, func(c clientcore.Client) error { - res, err = c.AnnounceIntermediateTrust(ctx, prm) - return err +func (x *multiClient) AnnounceIntermediateTrust(ctx context.Context, epoch uint64, trust reputationSDK.PeerToPeerTrust, prm client.PrmAnnounceIntermediateTrust) error { + return x.iterateClients(ctx, func(c clientcore.Client) error { + return c.AnnounceIntermediateTrust(ctx, epoch, trust, prm) }) - - return } func (x *multiClient) ExecRaw(f func(client *rawclient.Client) error) error { diff --git a/pkg/services/object/acl/acl.go b/pkg/services/object/acl/acl.go index 1b3fdb92551..83b6ac21dbb 100644 --- a/pkg/services/object/acl/acl.go +++ b/pkg/services/object/acl/acl.go @@ -10,7 +10,7 @@ import ( eaclV2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/eacl/v2" v2 "github.com/nspcc-dev/neofs-node/pkg/services/object/acl/v2" bearerSDK "github.com/nspcc-dev/neofs-sdk-go/bearer" - "github.com/nspcc-dev/neofs-sdk-go/client" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/container/acl" eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -130,7 +130,7 @@ func (c *Checker) CheckEACL(msg any, reqInfo v2.RequestInfo) error { if bearerTok == nil { eaclInfo, err := c.eaclSrc.GetEACL(cnr) if err != nil { - if client.IsErrEACLNotFound(err) { + if errors.Is(err, apistatus.ErrEACLNotFound) { return nil } return err diff --git a/pkg/services/object/get/v2/util.go b/pkg/services/object/get/v2/util.go index 597d9e5162e..ef5de1cd2ab 100644 --- a/pkg/services/object/get/v2/util.go +++ b/pkg/services/object/get/v2/util.go @@ -696,8 +696,7 @@ func writeCurrentVersion(metaHdr *session.RequestMetaHeader) { func checkStatus(stV2 *status.Status) error { if !status.IsSuccess(stV2.Code()) { - st := apistatus.FromStatusV2(stV2) - return apistatus.ErrFromStatus(st) + return apistatus.ErrorFromV2(stV2) } return nil diff --git a/pkg/services/object/internal/client/client.go b/pkg/services/object/internal/client/client.go index e91b22cfc25..53662623782 100644 --- a/pkg/services/object/internal/client/client.go +++ b/pkg/services/object/internal/client/client.go @@ -100,6 +100,7 @@ type GetObjectPrm struct { cliPrm client.PrmObjectGet obj oid.ID + cnr cid.ID } // SetRawFlag sets raw flag of the request. @@ -114,8 +115,7 @@ func (x *GetObjectPrm) SetRawFlag() { // Required parameter. func (x *GetObjectPrm) SetAddress(addr oid.Address) { x.obj = addr.Object() - x.cliPrm.FromContainer(addr.Container()) - x.cliPrm.ByID(x.obj) + x.cnr = addr.Container() } // GetObjectRes groups the resulting values of GetObject operation. @@ -159,7 +159,7 @@ func GetObject(prm GetObjectPrm) (*GetObjectRes, error) { prm.cliPrm.UseSigner(neofsecdsa.SignerRFC6979(*prm.key)) } - rdr, err := prm.cli.ObjectGetInit(prm.ctx, prm.cliPrm) + rdr, err := prm.cli.ObjectGetInit(prm.ctx, prm.cnr, prm.obj, prm.cliPrm) if err != nil { return nil, fmt.Errorf("init object reading: %w", err) } @@ -167,13 +167,8 @@ func GetObject(prm GetObjectPrm) (*GetObjectRes, error) { var obj object.Object if !rdr.ReadHeader(&obj) { - res, err := rdr.Close() - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(res.Status()) - } else { - ReportError(prm.cli, err) - } + err = rdr.Close() + ReportError(prm.cli, err) return nil, fmt.Errorf("read object header: %w", err) } @@ -199,6 +194,7 @@ type HeadObjectPrm struct { cliPrm client.PrmObjectHead obj oid.ID + cnr cid.ID } // SetRawFlag sets raw flag of the request. @@ -213,8 +209,7 @@ func (x *HeadObjectPrm) SetRawFlag() { // Required parameter. func (x *HeadObjectPrm) SetAddress(addr oid.Address) { x.obj = addr.Object() - x.cliPrm.FromContainer(addr.Container()) - x.cliPrm.ByID(x.obj) + x.cnr = addr.Container() } // HeadObjectRes groups the resulting values of GetObject operation. @@ -254,12 +249,7 @@ func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) { prm.cliPrm.WithXHeaders(prm.xHeaders...) - cliRes, err := prm.cli.ObjectHead(prm.ctx, prm.cliPrm) - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(cliRes.Status()) - } - + cliRes, err := prm.cli.ObjectHead(prm.ctx, prm.cnr, prm.obj, prm.cliPrm) if err != nil { return nil, fmt.Errorf("read object header from NeoFS: %w", err) } @@ -279,11 +269,12 @@ func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) { type PayloadRangePrm struct { readPrmCommon - ln uint64 + offset, ln uint64 cliPrm client.PrmObjectRange obj oid.ID + cnr cid.ID } // SetRawFlag sets raw flag of the request. @@ -298,15 +289,14 @@ func (x *PayloadRangePrm) SetRawFlag() { // Required parameter. func (x *PayloadRangePrm) SetAddress(addr oid.Address) { x.obj = addr.Object() - x.cliPrm.FromContainer(addr.Container()) - x.cliPrm.ByID(x.obj) + x.cnr = addr.Container() } // SetRange range of the object payload to be read. // // Required parameter. func (x *PayloadRangePrm) SetRange(rng *object.Range) { - x.cliPrm.SetOffset(rng.GetOffset()) + x.offset = rng.GetOffset() x.ln = rng.GetLength() } @@ -351,10 +341,9 @@ func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) { prm.cliPrm.WithBearerToken(*prm.tokenBearer) } - prm.cliPrm.SetLength(prm.ln) prm.cliPrm.WithXHeaders(prm.xHeaders...) - rdr, err := prm.cli.ObjectRangeInit(prm.ctx, prm.cliPrm) + rdr, err := prm.cli.ObjectRangeInit(prm.ctx, prm.cnr, prm.obj, prm.offset, prm.ln, prm.cliPrm) if err != nil { return nil, fmt.Errorf("init payload reading: %w", err) } @@ -440,13 +429,8 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) { } cliRes, err := w.Close() - if err == nil { - err = apistatus.ErrFromStatus(cliRes.Status()) - } else { - ReportError(prm.cli, err) - } - if err != nil { + ReportError(prm.cli, err) return nil, fmt.Errorf("write object via client: %w", err) } @@ -459,6 +443,7 @@ func PutObject(prm PutObjectPrm) (*PutObjectRes, error) { type SearchObjectsPrm struct { readPrmCommon + cid cid.ID cliPrm client.PrmObjectSearch } @@ -466,7 +451,7 @@ type SearchObjectsPrm struct { // // Required parameter. func (x *SearchObjectsPrm) SetContainerID(id cid.ID) { - x.cliPrm.InContainer(id) + x.cid = id } // SetFilters sets search filters. @@ -506,7 +491,7 @@ func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) { prm.cliPrm.UseSigner(neofsecdsa.SignerRFC6979(*prm.key)) } - rdr, err := prm.cli.ObjectSearchInit(prm.ctx, prm.cliPrm) + rdr, err := prm.cli.ObjectSearchInit(prm.ctx, prm.cid, prm.cliPrm) if err != nil { return nil, fmt.Errorf("init object searching in client: %w", err) } @@ -530,12 +515,7 @@ func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) { } } - res, err := rdr.Close() - if err == nil { - // pull out an error from status - err = apistatus.ErrFromStatus(res.Status()) - } - + err = rdr.Close() if err != nil { return nil, fmt.Errorf("read object list: %w", err) } diff --git a/pkg/services/policer/check.go b/pkg/services/policer/check.go index 8f9506ba4be..3e88b25366a 100644 --- a/pkg/services/policer/check.go +++ b/pkg/services/policer/check.go @@ -9,7 +9,6 @@ import ( "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" headsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/head" "github.com/nspcc-dev/neofs-node/pkg/services/replicator" - "github.com/nspcc-dev/neofs-sdk-go/client" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -269,7 +268,7 @@ func (p *Policer) processNodes(ctx *processPlacementContext, nodes []netmap.Node cancel() - if client.IsErrObjectNotFound(err) { + if errors.Is(err, apistatus.ErrObjectNotFound) { ctx.checkedNodes.submitReplicaCandidate(nodes[i]) continue } diff --git a/pkg/services/tree/signature.go b/pkg/services/tree/signature.go index 56807458e0d..d0c39ed33c7 100644 --- a/pkg/services/tree/signature.go +++ b/pkg/services/tree/signature.go @@ -9,7 +9,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/refs" core "github.com/nspcc-dev/neofs-node/pkg/core/container" "github.com/nspcc-dev/neofs-sdk-go/bearer" - "github.com/nspcc-dev/neofs-sdk-go/client" + statusSDK "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/container/acl" cidSDK "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" @@ -103,7 +103,7 @@ func (s *Service) verifyClient(req message, cid cidSDK.ID, rawBearer []byte, op } else { tbCore, err := s.eaclSource.GetEACL(cid) if err != nil { - if client.IsErrEACLNotFound(err) { + if errors.Is(err, statusSDK.ErrEACLNotFound) { return nil } diff --git a/pkg/services/util/sign.go b/pkg/services/util/sign.go index d03a46049a5..5fdec29b7b9 100644 --- a/pkg/services/util/sign.go +++ b/pkg/services/util/sign.go @@ -226,7 +226,7 @@ func setStatusV2(resp ResponseMessage, err error) { err = e } - session.SetStatus(resp, apistatus.ToStatusV2(apistatus.ErrToStatus(err))) + session.SetStatus(resp, apistatus.ErrorToV2(err)) } // signs response with private key via signature.SignServiceMessage. From 7384e8cd4d2fc729998a0772f137f1e6bb3ccb0d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 27 Jun 2023 22:42:44 +0400 Subject: [PATCH 7/9] sidechain/deploy: Auto-update on-chain NeoFS NNS smart contract There is a need to automatically update on-chain NNS contract to new code embedded into application importing `contracts` package. Sidechain deployment procedure fits well for this purpose. It already initializes Notary service for the committee members, and the update procedure (`update` method of each system contract) just requires a committee witness. Add new stage (currently last one) to Sidechain deployment procedure that updates on-chain NNS contract. Since 'update' methods require committee witness, also run background routine which signs incoming Notary requests on behalf of the local account. The routine will be useful for deployment/update of other NeoFS contracts (to be implemented in the future). Signed-off-by: Leonard Lyubich --- go.mod | 2 + go.sum | 3 + pkg/morph/deploy/contracts.go | 7 + pkg/morph/deploy/deploy.go | 61 ++++- pkg/morph/deploy/group.go | 12 +- pkg/morph/deploy/nns.go | 176 ++++++++++++++- pkg/morph/deploy/notary.go | 404 +++++++++++++++++++++++++++++++--- pkg/morph/deploy/util.go | 132 ++++++++++- pkg/morph/deploy/util_test.go | 154 +++++++++++++ 9 files changed, 904 insertions(+), 47 deletions(-) create mode 100644 pkg/morph/deploy/contracts.go create mode 100644 pkg/morph/deploy/util_test.go diff --git a/go.mod b/go.mod index 3f26cad57cb..99dd1211a17 100644 --- a/go.mod +++ b/go.mod @@ -94,11 +94,13 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.4.0 // indirect golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect + golang.org/x/mod v0.6.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.1.0 // indirect + golang.org/x/tools v0.2.0 // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index f1b4b142c48..1b52dc55fd6 100644 --- a/go.sum +++ b/go.sum @@ -256,6 +256,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -572,6 +573,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -780,6 +782,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/morph/deploy/contracts.go b/pkg/morph/deploy/contracts.go new file mode 100644 index 00000000000..dcce0d988e2 --- /dev/null +++ b/pkg/morph/deploy/contracts.go @@ -0,0 +1,7 @@ +package deploy + +// various common methods of the NeoFS contracts. +const ( + methodUpdate = "update" + methodVersion = "version" +) diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go index bb22e93ec61..fe3b7c257a3 100644 --- a/pkg/morph/deploy/deploy.go +++ b/pkg/morph/deploy/deploy.go @@ -11,7 +11,8 @@ import ( "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/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "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" @@ -21,9 +22,9 @@ import ( // 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 + // RPCActor groups functions needed to compose and send transactions (incl. + // Notary service requests) to the blockchain. + notary.RPCActor // GetCommittee returns list of public keys owned by Neo blockchain committee // members. Resulting list is non-empty, unique and unsorted. @@ -40,7 +41,14 @@ type Blockchain interface { // 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. + // ReceiveNotaryRequests starts background process that forwards new notary + // requests of the blockchain to the provided channel. The process skips + // requests that don't match specified filter. Returns unique identifier to be + // used to stop the process via Unsubscribe. + ReceiveNotaryRequests(*neorpc.TxFilter, chan<- *result.NotaryRequestEvent) (string, error) + + // Unsubscribe stops background process started by ReceiveBlocks or + // ReceiveNotaryRequests by ID. Unsubscribe(id string) error } @@ -93,7 +101,7 @@ type Prm struct { // 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) +// 4. deployment/update of the NeoFS system contracts (currently only NNS) // 5. deployment of custom contracts // // See project documentation for details. @@ -190,6 +198,22 @@ func Deploy(ctx context.Context, prm Prm) error { prm.Logger.Info("Notary service successfully enabled for the committee") + onNotaryDepositDeficiency, err := initNotaryDepositDeficiencyHandler(prm.Logger, prm.Blockchain, monitor, prm.LocalAccount) + if err != nil { + return fmt.Errorf("construct action depositing funds to the local account's Notary balance: %w", err) + } + + err = listenCommitteeNotaryRequests(ctx, listenCommitteeNotaryRequestsPrm{ + logger: prm.Logger, + blockchain: prm.Blockchain, + localAcc: prm.LocalAccount, + committee: committee, + onNotaryDepositDeficiency: onNotaryDepositDeficiency, + }) + if err != nil { + return fmt.Errorf("start listener of committee notary requests: %w", err) + } + prm.Logger.Info("initializing committee group for contract management...") committeeGroupKey, err := initCommitteeGroup(ctx, initCommitteeGroupPrm{ @@ -209,7 +233,30 @@ func Deploy(ctx context.Context, prm Prm) error { prm.Logger.Info("committee group successfully initialized", zap.Stringer("public key", committeeGroupKey.PublicKey())) - // TODO: deploy contracts + prm.Logger.Info("updating on-chain NNS contract...") + + err = updateNNSContract(ctx, updateNNSContractPrm{ + logger: prm.Logger, + blockchain: prm.Blockchain, + monitor: monitor, + localAcc: prm.LocalAccount, + localNEF: prm.NNS.Common.NEF, + localManifest: prm.NNS.Common.Manifest, + systemEmail: prm.NNS.SystemEmail, + committee: committee, + committeeGroupKey: committeeGroupKey, + buildVersionedExtraUpdateArgs: noExtraUpdateArgs, + onNotaryDepositDeficiency: onNotaryDepositDeficiency, + }) + if err != nil { + return fmt.Errorf("update NNS contract on the chain: %w", err) + } + + prm.Logger.Info("on-chain NNS contract successfully updated") + + // TODO: deploy/update other contracts return nil } + +func noExtraUpdateArgs(contractVersion) ([]interface{}, error) { return nil, nil } diff --git a/pkg/morph/deploy/group.go b/pkg/morph/deploy/group.go index 69a6ca74126..d94e51676cc 100644 --- a/pkg/morph/deploy/group.go +++ b/pkg/morph/deploy/group.go @@ -124,12 +124,12 @@ upperLoop: // context of the committee group key distribution by leading committee member // between calls. func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committeeGroupKey *keys.PrivateKey) (func(), error) { - _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) } - _invoker := invoker.New(prm.blockchain, nil) + invkr := invoker.New(prm.blockchain, nil) // multi-tick context mDomainsToVubs := make(map[string][2]uint32) // 1st - register, 2nd - addRecord @@ -143,7 +143,7 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee l.Info("synchronizing committee group key with NNS domain record...") - _, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + _, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domain) if err != nil { if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") @@ -163,8 +163,8 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, - domain, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domain, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { vubs[0] = 0 mDomainsToVubs[domain] = vubs @@ -213,7 +213,7 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee l.Info("sending new transaction setting domain record in the NNS...") - _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domain, int64(nns.TXT), keyCipher) if err != nil { vubs[1] = 0 diff --git a/pkg/morph/deploy/nns.go b/pkg/morph/deploy/nns.go index 1b1ebf2cdaf..ec0972c9067 100644 --- a/pkg/morph/deploy/nns.go +++ b/pkg/morph/deploy/nns.go @@ -2,6 +2,7 @@ package deploy import ( "context" + "encoding/json" "errors" "fmt" "strings" @@ -158,13 +159,13 @@ func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Ui prm.logger.Info("sending new transaction deploying NNS contract...") if managementContract == nil { - _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { prm.logger.Warn("NNS contract is missing on the chain but attempts to deploy are disabled, will try again later") continue } - managementContract = management.New(_actor) + managementContract = management.New(localActor) setGroupInManifest(&prm.localManifest, prm.localNEF, committeeGroupKey, prm.localAcc.ScriptHash()) } @@ -226,3 +227,174 @@ func lookupNNSDomainRecord(inv *invoker.Invoker, nnsContract util.Uint160, domai return "", errMissingDomainRecord } + +// updateNNSContractPrm groups parameters of NeoFS NNS contract update. +type updateNNSContractPrm struct { + logger *zap.Logger + + blockchain Blockchain + + // based on blockchain + monitor *blockchainMonitor + + localAcc *wallet.Account + + localNEF nef.File + localManifest manifest.Manifest + systemEmail string + + committee keys.PublicKeys + committeeGroupKey *keys.PrivateKey + + // constructor of extra arguments to be passed into method updating the + // contract. If returns both nil, no data is passed (noExtraUpdateArgs may be + // used). + buildVersionedExtraUpdateArgs func(versionOnChain contractVersion) ([]interface{}, error) + + onNotaryDepositDeficiency notaryDepositDeficiencyHandler +} + +// updateNNSContract synchronizes on-chain NNS contract (its presence is a +// precondition) with the local one represented by compiled executables. If +// on-chain version is greater or equal to the local one, nothing happens. +// Otherwise, transaction calling 'update' method is sent. +// +// Local manifest is extended with committee group represented by the +// parameterized private key. +// +// Function behaves similar to initNNSContract in terms of context. +func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { + bLocalNEF, err := prm.localNEF.Bytes() + if err != nil { + // not really expected + return fmt.Errorf("encode local NEF of the NNS contract into binary: %w", err) + } + + jLocalManifest, err := json.Marshal(prm.localManifest) + if err != nil { + // not really expected + return fmt.Errorf("encode local manifest of the NNS contract into JSON: %w", err) + } + + committeeActor, err := newCommitteeNotaryActor(prm.blockchain, prm.localAcc, prm.committee) + if err != nil { + return fmt.Errorf("create Notary service client sending transactions to be signed by the committee: %w", err) + } + + localVersion, err := readContractLocalVersion(prm.blockchain, prm.localNEF, prm.localManifest) + if err != nil { + return fmt.Errorf("read version of the local NNS contract: %w", err) + } + + var updateTxValidUntilBlock uint32 + + for ; ; prm.monitor.waitForNextBlock(ctx) { + select { + case <-ctx.Done(): + return fmt.Errorf("wait for NNS contract synchronization: %w", ctx.Err()) + default: + } + + prm.logger.Info("reading on-chain state of the NNS contract...") + + nnsOnChainState, err := readNNSOnChainState(prm.blockchain) + if err != nil { + prm.logger.Error("failed to read on-chain state of the NNS contract, will try again later", zap.Error(err)) + continue + } else if nnsOnChainState == nil { + // NNS contract must be deployed at this stage + return errors.New("missing required NNS contract on the chain") + } + + if nnsOnChainState.NEF.Checksum == prm.localNEF.Checksum { + // manifests may differ, but currently we should bump internal contract version + // (i.e. change NEF) to make such updates. Right now they are not supported due + // to dubious practical need + // Track https://github.com/nspcc-dev/neofs-contract/issues/340 + prm.logger.Info("same local and on-chain checksums of the NNS contract NEF, update is not needed") + return nil + } + + prm.logger.Info("NEF checksums of the on-chain and local NNS contracts differ, need an update") + + versionOnChain, err := readContractOnChainVersion(prm.blockchain, nnsOnChainState.Hash) + if err != nil { + prm.logger.Error("failed to read on-chain version of the NNS contract, will try again later", zap.Error(err)) + continue + } + + if v := localVersion.cmp(versionOnChain); v == -1 { + prm.logger.Info("local contract version is < than the on-chain one, update is not needed", + zap.Stringer("local", localVersion), zap.Stringer("on-chain", versionOnChain)) + return nil + } else if v == 0 { + return fmt.Errorf("local and on-chain contracts have different NEF checksums but same version '%s'", versionOnChain) + } + + extraUpdateArgs, err := prm.buildVersionedExtraUpdateArgs(versionOnChain) + if err != nil { + prm.logger.Error("failed to prepare build extra arguments for NNS contract update, will try again later", + zap.Stringer("on-chain version", versionOnChain), zap.Error(err)) + continue + } + + setGroupInManifest(&prm.localManifest, prm.localNEF, prm.committeeGroupKey, prm.localAcc.ScriptHash()) + + var vub uint32 + + // we pre-check 'already updated' case via MakeCall in order to not potentially + // wait for previously sent transaction to be expired (condition below) and + // immediately succeed + tx, err := committeeActor.MakeCall(nnsOnChainState.Hash, methodUpdate, + bLocalNEF, jLocalManifest, extraUpdateArgs) + if err != nil { + if isErrContractAlreadyUpdated(err) { + // note that we can come here only if local version is > than the on-chain one + // (compared above) + prm.logger.Info("NNS contract has already been updated, skip") + return nil + } + } else { + if updateTxValidUntilBlock > 0 { + prm.logger.Info("transaction updating NNS contract was sent earlier, checking relevance...") + + if cur := prm.monitor.currentHeight(); cur <= updateTxValidUntilBlock { + prm.logger.Info("previously sent transaction updating NNS contract may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", updateTxValidUntilBlock)) + continue + } + + prm.logger.Info("previously sent transaction updating NNS contract expired without side-effect") + } + + prm.logger.Info("sending new transaction updating NNS contract...") + + _, _, vub, err = committeeActor.Notarize(tx, nil) + } + if err != nil { + lackOfGAS := isErrNotEnoughGAS(err) + // here lackOfGAS=true always means lack of Notary balance and not related to + // the main transaction itself + if !lackOfGAS { + if !isErrNotaryDepositExpires(err) { + prm.logger.Error("failed to send transaction deploying NNS contract, will try again later", zap.Error(err)) + continue + } + } + + // same approach with in-place deposit is going to be used in other functions. + // Consider replacement with background process (e.g. blockchainMonitor + // internal) which periodically checks Notary balance and updates it when, for + // example, balance goes lower than 20% of desired amount or expires soon. With + // this approach functions like current will not try to make a deposit, but + // simply wait until it becomes enough. + prm.onNotaryDepositDeficiency(lackOfGAS) + + continue + } + + updateTxValidUntilBlock = vub + + prm.logger.Info("transaction updating NNS contract has been successfully sent, will wait for the outcome") + } +} diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go index 470a79e4c6a..8e28dafb6c8 100644 --- a/pkg/morph/deploy/notary.go +++ b/pkg/morph/deploy/notary.go @@ -3,21 +3,30 @@ package deploy import ( "bytes" "context" + "crypto/elliptic" "crypto/sha256" "encoding/base64" "encoding/binary" "errors" "fmt" + "math" + "math/big" + "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" "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/neorpc" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" + "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/vm" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/wallet" randutil "github.com/nspcc-dev/neofs-node/pkg/util/rand" @@ -110,12 +119,12 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { // initDesignateNotaryRoleToLocalAccountTick returns a function that preserves // context of the Notary role designation to the local account between calls. func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), error) { - _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) } - roleContract := rolemgmt.New(_actor) + roleContract := rolemgmt.New(localActor) // multi-tick context var sentTxValidUntilBlock uint32 @@ -172,7 +181,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return nil, fmt.Errorf("compose committee multi-signature account: %w", err) } - _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) } @@ -199,7 +208,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return nil, fmt.Errorf("init transaction sender with committee signers: %w", err) } - _invoker := invoker.New(prm.blockchain, nil) + invkr := invoker.New(prm.blockchain, nil) roleContract := rolemgmt.New(committeeActor) // multi-tick context @@ -238,7 +247,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return } - // _actor.CalculateValidUntilBlock is not used because it is rather "idealized" + // localActor.CalculateValidUntilBlock is not used because it is rather "idealized" // in terms of the accessibility of committee member nodes. So, we need a more // practically viable timeout to reduce the chance of transaction re-creation. const defaultValidUntilBlockIncrement = 120 // ~30m for 15s block interval @@ -251,7 +260,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { } strSharedTxData := sharedTransactionData{ - sender: _actor.Sender(), + sender: localActor.Sender(), validUntilBlock: txValidUntilBlock, nonce: randutil.Uint32(), }.encodeToString() @@ -260,10 +269,10 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { var vub uint32 if recordExists { - _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, domainDesignateNotaryTx, int64(nns.TXT), 0, strSharedTxData) } else { - _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domainDesignateNotaryTx, int64(nns.TXT), strSharedTxData) } if err != nil { @@ -281,7 +290,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") } - strSharedTxData, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domainDesignateNotaryTx) + strSharedTxData, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domainDesignateNotaryTx) if err != nil { if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") @@ -300,8 +309,8 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, - domainDesignateNotaryTx, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domainDesignateNotaryTx, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { registerDomainTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { @@ -371,7 +380,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { prm.logger.Info("transaction designating Notary role to the committee initialized, signing...") - netMagic := _actor.GetNetwork() + netMagic := localActor.GetNetwork() err = prm.localAcc.SignTx(netMagic, tx) if err != nil { @@ -406,7 +415,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { for i := range prm.committee[1:] { domain := designateNotarySignatureDomainForMember(i) - rec, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + rec, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domain) if err != nil { if errors.Is(err, errMissingDomain) || errors.Is(err, errMissingDomainRecord) { prm.logger.Info("missing NNS domain record with committee member's signature of the transaction designating Notary role to the committee, will wait", @@ -439,7 +448,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { } txCp := *tx // to safely call Hash method below - if !prm.committee[i].VerifyHashable(bSignature, uint32(_actor.GetNetwork()), &txCp) { + if !prm.committee[i].VerifyHashable(bSignature, uint32(localActor.GetNetwork()), &txCp) { prm.logger.Info("invalid signature of the transaction designating Notary role to the committee submitted by committee member", zap.Stringer("member", prm.committee[i]), zap.String("domain", domain)) @@ -516,7 +525,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { prm.logger.Info("sending the transaction designating Notary role to the committee...") - _, vub, err := _actor.Send(tx) + _, vub, err := localActor.Send(tx) if err != nil { designateRoleTxValidUntilBlock = 0 switch { @@ -552,7 +561,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { return nil, fmt.Errorf("compose committee multi-signature account: %w", err) } - _actor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) } @@ -579,7 +588,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { return nil, fmt.Errorf("init transaction sender with committee signers: %w", err) } - _invoker := invoker.New(prm.blockchain, nil) + invkr := invoker.New(prm.blockchain, nil) roleContract := rolemgmt.New(committeeActor) // multi-tick context @@ -597,7 +606,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { prm.logger.Info("synchronizing shared data of the transaction designating Notary role to the committee with NNS domain record...") - strSharedTxData, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domainDesignateNotaryTx) + strSharedTxData, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domainDesignateNotaryTx) if err != nil { switch { default: @@ -653,7 +662,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { var recordExists bool var needReset bool - rec, err := lookupNNSDomainRecord(_invoker, prm.nnsOnChainAddress, domain) + rec, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domain) if err != nil { if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") @@ -672,8 +681,8 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := _actor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, - domain, _actor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) + _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + domain, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { registerDomainTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { @@ -723,7 +732,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { needReset = true } else { txCp := *tx // to safely call Hash method below - if !prm.localAcc.PublicKey().VerifyHashable(bSignature, uint32(_actor.GetNetwork()), &txCp) { + if !prm.localAcc.PublicKey().VerifyHashable(bSignature, uint32(localActor.GetNetwork()), &txCp) { l.Info("invalid signature of the transaction designating Notary role to the committee submitted by local account, need to be recalculated") needReset = true } @@ -735,7 +744,7 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { if needReset { prm.logger.Info("calculating signature of the transaction designating Notary role to the committee using local account...") - sig := prm.localAcc.SignHashable(_actor.GetNetwork(), tx) + sig := prm.localAcc.SignHashable(localActor.GetNetwork(), tx) sig = sharedTxData.unshiftChecksum(sig) rec = base64.StdEncoding.EncodeToString(sig) @@ -744,10 +753,10 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { var vub uint32 if recordExists { - _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, domain, int64(nns.TXT), 0, rec) } else { - _, vub, err = _actor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domain, int64(nns.TXT), rec) } if err != nil { @@ -869,3 +878,348 @@ func makeUnsignedDesignateCommitteeNotaryTx(roleContract *rolemgmt.Contract, com return tx, nil } + +// newCommitteeNotaryActor returns notary.Actor that builds and sends Notary +// service requests witnessed by the specified committee members to the provided +// Blockchain. Given local account pays for transactions. +func newCommitteeNotaryActor(b Blockchain, localAcc *wallet.Account, committee keys.PublicKeys) (*notary.Actor, error) { + committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(committee)) + committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(localAcc.PrivateKey()) + + err := committeeMultiSigAcc.ConvertMultisig(committeeMultiSigM, committee) + if err != nil { + return nil, fmt.Errorf("compose committee multi-signature account: %w", err) + } + + return notary.NewActor(b, []actor.SignerAccount{ + { + Signer: transaction.Signer{ + Account: localAcc.ScriptHash(), + Scopes: transaction.None, + }, + Account: localAcc, + }, + { + Signer: transaction.Signer{ + Account: committeeMultiSigAcc.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + Account: committeeMultiSigAcc, + }, + }, localAcc) +} + +// notaryDepositDeficiencyHandler is a function returned by initNotaryDepositDeficiencyHandler. +// True argument is passed when there is not enough GAS on local account's +// balance in the Notary contract, false - when local account's Notary deposit +// expires before particular fallback transaction. +// +// The function is intended to be called multiple times on each deposit problem +// encounter. On each call, It attempts to fix Notary deposit problem without +// waiting for success. Caller should by default wait for the problem to be +// fixed, and if not, retry. +// +// notaryDepositDeficiencyHandler must not be called from multiple routines in +// parallel. +type notaryDepositDeficiencyHandler = func(lackOfGAS bool) + +// Amount of GAS for the single local account's GAS->Notary transfer. Relatively +// small value for fallback transactions' fees. +var singleNotaryDepositAmount = big.NewInt(1_0000_0000) // 1 GAS + +// constructs notaryDepositDeficiencyHandler working with the specified +// Blockchain and GAS/Notary balance of the given account. +func initNotaryDepositDeficiencyHandler(l *zap.Logger, b Blockchain, monitor *blockchainMonitor, localAcc *wallet.Account) (notaryDepositDeficiencyHandler, error) { + localActor, err := actor.NewSimple(b, localAcc) + if err != nil { + return nil, fmt.Errorf("init transaction sender from local account: %w", err) + } + + notaryContract := notary.New(localActor) + gasContract := gas.New(localActor) + localAccID := localAcc.ScriptHash() + + // multi-tick context + var transferTxValidUntilBlock uint32 + var expirationTxValidUntilBlock uint32 + + return func(lackOfGAS bool) { + notaryBalance, err := notaryContract.BalanceOf(localAccID) + if err != nil { + l.Error("failed to read Notary balance of the local account, will try again later", zap.Error(err)) + return + } + + gasBalance, err := gasContract.BalanceOf(localAccID) + if err != nil { + l.Error("failed to read GAS token balance of the local account, will try again later", zap.Error(err)) + return + } + + // simple deposit scheme: transfer 1GAS (at most 2% of GAS token balance) for + // 100 blocks after the latest deposit's expiration height (if first, then from + // current height). + // + // If we encounter deposit expiration and current Notary balance >=20% of single + // transfer, we just increase the expiration time of the deposit, otherwise, we + // make transfer. + + const ( + // GAS:Notary proportion, see scheme above + gasProportion = 50 + // even there is no lack of GAS at the moment, when the balance falls below 1/5 + // of the supported value - replenish + refillProportion = 5 + // for simplicity, we just make Notary deposit "infinite" not to prolong + till = math.MaxUint32 + ) + + if !lackOfGAS { // deposit expired + if new(big.Int).Mul(notaryBalance, big.NewInt(refillProportion)).Cmp(singleNotaryDepositAmount) >= 0 { + if expirationTxValidUntilBlock > 0 { + l.Info("transaction increasing expiration time of the Notary deposit was sent earlier, checking relevance...") + + if cur := monitor.currentHeight(); cur <= expirationTxValidUntilBlock { + l.Info("previously sent transaction increasing expiration time of the Notary deposit may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", expirationTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction increasing expiration time of the Notary deposit expired without side-effect ") + } + + l.Info("sending new transaction increasing expiration time of the Notary deposit...", zap.Uint32("till", till)) + + _, vub, err := notaryContract.LockDepositUntil(localAccID, till) + if err != nil { + l.Error("failed to send transaction increasing expiration time of the Notary deposit, will try again later", zap.Error(err)) + return + } + + expirationTxValidUntilBlock = vub + + l.Info("transaction increasing expiration time of the Notary deposit has been successfully sent, will wait for the outcome") + + return + } + } + + if transferTxValidUntilBlock > 0 { + l.Info("transaction transferring local account's GAS to the Notary contract was sent earlier, checking relevance...") + + // for simplicity, we track ValidUntilBlock. In this particular case, it'd be + // more efficient to monitor a transaction by ID, because side effect is + // inconsistent (funds can be spent in background). + + if cur := monitor.currentHeight(); cur <= transferTxValidUntilBlock { + l.Info("previously sent transaction transferring local account's GAS to the Notary contract may still be relevant, will wait for the outcome", + zap.Uint32("current height", cur), zap.Uint32("retry after height", transferTxValidUntilBlock)) + return + } + + l.Info("previously sent transaction transferring local account's GAS to the Notary contract expired without side-effect") + } + + needAtLeast := new(big.Int).Mul(singleNotaryDepositAmount, big.NewInt(gasProportion)) + if gasBalance.Cmp(needAtLeast) < 0 { + l.Info("minimum threshold for GAS transfer from local account to the Notary contract not reached, will wait for replenishment", + zap.Stringer("need at least", needAtLeast), zap.Stringer("have", gasBalance)) + return + } + + var transferData notary.OnNEP17PaymentData + transferData.Account = &localAccID + transferData.Till = till + + l.Info("sending new transaction transferring local account's GAS to the Notary contract...", + zap.Stringer("amount", singleNotaryDepositAmount), zap.Uint32("till", transferData.Till)) + + // nep17.TokenWriter.Transfer doesn't support notary.OnNEP17PaymentData + // directly, so split the args + // Track https://github.com/nspcc-dev/neofs-node/issues/2429 + _, vub, err := gasContract.Transfer(localAccID, notary.Hash, singleNotaryDepositAmount, []interface{}{transferData.Account, transferData.Till}) + if err != nil { + l.Error("failed to send transaction transferring local account's GAS to the Notary contract, will try again later", zap.Error(err)) + return + } + + transferTxValidUntilBlock = vub + + l.Info("transaction transferring local account's GAS to the Notary contract has been successfully sent, will wait for the outcome") + }, nil +} + +// listenCommitteeNotaryRequestsPrm groups parameters of listenCommitteeNotaryRequests. +type listenCommitteeNotaryRequestsPrm struct { + logger *zap.Logger + + blockchain Blockchain + + localAcc *wallet.Account + + committee keys.PublicKeys + + onNotaryDepositDeficiency notaryDepositDeficiencyHandler +} + +// listenCommitteeNotaryRequests starts background process listening to incoming +// Notary service requests. The process filters transactions witnessed by the +// committee and signs them on behalf of the local account (representing +// committee member). Routine handles only requests sent by the remote accounts. +// The process is stopped by context or internal Blockchain signal. +func listenCommitteeNotaryRequests(ctx context.Context, prm listenCommitteeNotaryRequestsPrm) error { + committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) + committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) + + err := committeeMultiSigAcc.ConvertMultisig(committeeMultiSigM, prm.committee) + if err != nil { + return fmt.Errorf("compose committee multi-signature account: %w", err) + } + + committeeMultiSigAccID := committeeMultiSigAcc.ScriptHash() + chNotaryRequests := make(chan *result.NotaryRequestEvent, 100) // secure from blocking + // cache processed operations: when main transaction from received notary + // request is signed and sent by the local account, we receive the request from + // the channel again + mProcessedMainTxs := make(map[util.Uint256]struct{}) + + subID, err := prm.blockchain.ReceiveNotaryRequests(&neorpc.TxFilter{ + Signer: &committeeMultiSigAccID, + }, chNotaryRequests) + if err != nil { + return fmt.Errorf("subscribe to notary requests from committee: %w", err) + } + + go func() { + defer func() { + err := prm.blockchain.Unsubscribe(subID) + if err != nil { + prm.logger.Warn("failed to cancel subscription to notary requests", zap.Error(err)) + } + }() + + prm.logger.Info("listening to committee notary requests...") + + for { + select { + case <-ctx.Done(): + prm.logger.Info("stop listening to committee notary requests (context is done)", zap.Error(ctx.Err())) + return + case notaryEvent, ok := <-chNotaryRequests: + if !ok { + prm.logger.Info("stop listening to committee notary requests (subscription channel closed)") + return + } + + // for simplicity, requests are handled one-by one. We could process them in parallel + // using worker pool, but actions seem to be relatively lightweight + + const expectedSignersCount = 3 // sender + committee + Notary + mainTx := notaryEvent.NotaryRequest.MainTransaction + // note: instruction above can throw NPE and it's ok to panic: we confidently + // expect that only non-nil pointers will come from the channel (NeoGo + // guarantees) + + srcMainTxHash := mainTx.Hash() + _, processed := mProcessedMainTxs[srcMainTxHash] + + // revise severity level of the messages + // https://github.com/nspcc-dev/neofs-node/issues/2419 + switch { + case processed: + prm.logger.Info("main transaction of the notary request has already been processed, skip", + zap.Stringer("ID", srcMainTxHash)) + continue + case notaryEvent.Type != mempoolevent.TransactionAdded: + prm.logger.Info("unsupported type of the notary request event, skip", + zap.Stringer("got", notaryEvent.Type), zap.Stringer("expect", mempoolevent.TransactionAdded)) + continue + case len(mainTx.Signers) != expectedSignersCount: + prm.logger.Info("unsupported number of signers of main transaction from the received notary request, skip", + zap.Int("expected", expectedSignersCount), zap.Int("got", len(mainTx.Signers))) + continue + case !mainTx.HasSigner(committeeMultiSigAccID): + prm.logger.Info("committee is not a signer of main transaction from the received notary request, skip") + continue + case mainTx.HasSigner(prm.localAcc.ScriptHash()): + prm.logger.Info("main transaction from the received notary request is signed by a local account, skip") + continue + case len(mainTx.Scripts) == 0: + prm.logger.Info("missing scripts of main transaction from the received notary request, skip") + continue + } + + bSenderKey, ok := vm.ParseSignatureContract(mainTx.Scripts[0].VerificationScript) + if !ok { + prm.logger.Info("first verification script in main transaction of the received notary request is not a signature one, skip", zap.Error(err)) + continue + } + + senderKey, err := keys.NewPublicKeyFromBytes(bSenderKey, elliptic.P256()) + if err != nil { + prm.logger.Info("failed to decode sender's public key from first script of main transaction from the received notary request, skip", zap.Error(err)) + continue + } + + // copy transaction to avoid pointer mutation + mainTxCp := *mainTx + mainTxCp.Scripts = nil + + mainTx = &mainTxCp // source one isn't needed anymore + + // it'd be safer to get into the transaction and analyze what it is trying to do. + // For simplicity, now we blindly sign it. Track https://github.com/nspcc-dev/neofs-node/issues/2430 + + prm.logger.Info("signing main transaction from the received notary request by the local account...") + + // create new actor for current signers. As a slight optimization, we could also + // compare with signers of previously created actor and deduplicate. + // See also https://github.com/nspcc-dev/neofs-node/issues/2314 + notaryActor, err := notary.NewActor(prm.blockchain, []actor.SignerAccount{ + { + Signer: mainTx.Signers[0], + Account: notary.FakeSimpleAccount(senderKey), + }, + { + Signer: mainTx.Signers[1], + Account: committeeMultiSigAcc, + }, + }, prm.localAcc) + if err != nil { + // not really expected + prm.logger.Error("failed to init Notary request sender with signers from the main transaction of the received notary request", zap.Error(err)) + continue + } + + err = notaryActor.Sign(mainTx) + if err != nil { + prm.logger.Error("failed to sign main transaction from the received notary request by the local account, skip", zap.Error(err)) + continue + } + + prm.logger.Info("sending new notary request with the main transaction signed by the local account...") + + _, _, _, err = notaryActor.Notarize(mainTx, nil) + if err != nil { + lackOfGAS := isErrNotEnoughGAS(err) + // here lackOfGAS=true always means lack of Notary balance and not related to + // the main transaction itself + if !lackOfGAS { + if !isErrNotaryDepositExpires(err) { + prm.logger.Error("failed to send transaction deploying NNS contract, will try again later", zap.Error(err)) + continue + } + } + + prm.onNotaryDepositDeficiency(lackOfGAS) + + continue + } + + prm.logger.Info("main transaction from the received notary request has been successfully signed and sent by the local account") + } + } + }() + + return nil +} diff --git a/pkg/morph/deploy/util.go b/pkg/morph/deploy/util.go index e2e7d18edac..fc164a4458c 100644 --- a/pkg/morph/deploy/util.go +++ b/pkg/morph/deploy/util.go @@ -2,18 +2,29 @@ package deploy import ( "context" + "encoding/json" "errors" "fmt" "strings" "time" "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "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/neorpc" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "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/nef" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neofs-contract/common" "go.uber.org/atomic" "go.uber.org/zap" ) @@ -26,33 +37,41 @@ func isErrContractNotFound(err error) bool { } func isErrNotEnoughGAS(err error) bool { - return errors.Is(err, neorpc.ErrValidationFailed) && strings.Contains(err.Error(), "insufficient funds") + return isErrInvalidTransaction(err) && strings.Contains(err.Error(), "insufficient funds") } func isErrInvalidTransaction(err error) bool { return errors.Is(err, neorpc.ErrValidationFailed) } -func setGroupInManifest(_manifest *manifest.Manifest, _nef nef.File, groupPrivKey *keys.PrivateKey, deployerAcc util.Uint160) { - contractAddress := state.CreateContractHash(deployerAcc, _nef.Checksum, _manifest.Name) +func isErrNotaryDepositExpires(err error) bool { + return strings.Contains(err.Error(), "fallback transaction is valid after deposit is unlocked") +} + +func isErrContractAlreadyUpdated(err error) bool { + return strings.Contains(err.Error(), common.ErrAlreadyUpdated) +} + +func setGroupInManifest(manif *manifest.Manifest, nefFile nef.File, groupPrivKey *keys.PrivateKey, deployerAcc util.Uint160) { + contractAddress := state.CreateContractHash(deployerAcc, nefFile.Checksum, manif.Name) sig := groupPrivKey.Sign(contractAddress.BytesBE()) groupPubKey := groupPrivKey.PublicKey() ind := -1 - for i := range _manifest.Groups { - if _manifest.Groups[i].PublicKey.Equal(groupPubKey) { + for i := range manif.Groups { + if manif.Groups[i].PublicKey.Equal(groupPubKey) { ind = i break } } if ind >= 0 { - _manifest.Groups[ind].Signature = sig + manif.Groups[ind].Signature = sig return } - _manifest.Groups = append(_manifest.Groups, manifest.Group{ + manif.Groups = append(manif.Groups, manifest.Group{ PublicKey: groupPubKey, Signature: sig, }) @@ -166,3 +185,102 @@ func readNNSOnChainState(b Blockchain) (*state.Contract, error) { } return res, nil } + +// contractVersion describes versioning of NeoFS smart contracts. +type contractVersion struct{ major, minor, patch uint64 } + +// space sizes for major and minor versions of the NeoFS contracts. +const majorSpace, minorSpace = 1e6, 1e3 + +// equals checks if contractVersion equals to the specified SemVer version. +// +//nolint:unused +func (x contractVersion) equals(major, minor, patch uint64) bool { + return x.major == major && x.minor == minor && x.patch == patch +} + +// returns contractVersion as single integer. +func (x contractVersion) toUint64() uint64 { + return x.major*majorSpace + x.minor*minorSpace + x.patch +} + +// cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +func (x contractVersion) cmp(y contractVersion) int { + xN := x.toUint64() + yN := y.toUint64() + if xN < yN { + return -1 + } else if xN == yN { + return 0 + } + return 1 +} + +func (x contractVersion) String() string { + const sep = "." + return fmt.Sprintf("%d%s%d%s%d", x.major, sep, x.minor, sep, x.patch) +} + +// parses contractVersion from the invocation result of methodVersion method. +func parseContractVersionFromInvocationResult(res *result.Invoke) (contractVersion, error) { + bigVersionOnChain, err := unwrap.BigInt(res, nil) + if err != nil { + return contractVersion{}, fmt.Errorf("unwrap big integer from '%s' method return: %w", methodVersion, err) + } else if !bigVersionOnChain.IsUint64() { + return contractVersion{}, fmt.Errorf("invalid/unsupported format of the '%s' method return: expected uint64, got %v", methodVersion, bigVersionOnChain) + } + + n := bigVersionOnChain.Uint64() + + mjr := n / majorSpace + + return contractVersion{ + major: mjr, + minor: (n - mjr*majorSpace) / minorSpace, + patch: n % minorSpace, + }, nil +} + +// readContractOnChainVersion returns current version of the smart contract +// presented in given Blockchain with specified address. +func readContractOnChainVersion(b Blockchain, onChainAddress util.Uint160) (contractVersion, error) { + res, err := invoker.New(b, nil).Call(onChainAddress, methodVersion) + if err != nil { + return contractVersion{}, fmt.Errorf("call '%s' contract method: %w", methodVersion, err) + } + + return parseContractVersionFromInvocationResult(res) +} + +// readContractLocalVersion returns version of the local smart contract +// represented by its compiled artifacts. +func readContractLocalVersion(rpc invoker.RPCInvoke, localNEF nef.File, localManifest manifest.Manifest) (contractVersion, error) { + jManifest, err := json.Marshal(localManifest) + if err != nil { + return contractVersion{}, fmt.Errorf("encode manifest into JSON: %w", err) + } + + bNEF, err := localNEF.Bytes() + if err != nil { + return contractVersion{}, fmt.Errorf("encode NEF into binary: %w", err) + } + + script := io.NewBufBinWriter() + emit.Opcodes(script.BinWriter, opcode.NEWARRAY0) + emit.Int(script.BinWriter, int64(callflag.All)) + emit.String(script.BinWriter, methodVersion) + emit.AppCall(script.BinWriter, management.Hash, "deploy", callflag.All, bNEF, jManifest) + emit.Opcodes(script.BinWriter, opcode.PUSH2, opcode.PICKITEM) + emit.Syscall(script.BinWriter, interopnames.SystemContractCall) + + res, err := invoker.New(rpc, nil).Run(script.Bytes()) + if err != nil { + return contractVersion{}, fmt.Errorf("run test script deploying contract and calling its '%s' method: %w", methodVersion, err) + } + + return parseContractVersionFromInvocationResult(res) +} diff --git a/pkg/morph/deploy/util_test.go b/pkg/morph/deploy/util_test.go new file mode 100644 index 00000000000..2d2816e5cdb --- /dev/null +++ b/pkg/morph/deploy/util_test.go @@ -0,0 +1,154 @@ +package deploy + +import ( + "math" + "math/big" + "strconv" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "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/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" + "github.com/stretchr/testify/require" +) + +func TestVersionCmp(t *testing.T) { + for _, tc := range []struct { + xmjr, xmnr, xpatch uint64 + ymjr, ymnr, ypatch uint64 + expected int + }{ + { + 1, 2, 3, + 1, 2, 3, + 0, + }, + { + 0, 1, 1, + 0, 2, 0, + -1, + }, + { + 1, 2, 2, + 1, 2, 3, + -1, + }, + { + 1, 2, 4, + 1, 2, 3, + 1, + }, + { + 0, 10, 0, + 1, 2, 3, + -1, + }, + { + 2, 0, 0, + 1, 2, 3, + 1, + }, + } { + x := contractVersion{tc.xmjr, tc.xmnr, tc.xpatch} + y := contractVersion{tc.ymjr, tc.ymnr, tc.ypatch} + require.Equal(t, tc.expected, x.cmp(y), tc) + } +} + +func TestParseContractVersionFromInvocationResult(t *testing.T) { + var err error + var res result.Invoke + + // non-HALT state + _, err = parseContractVersionFromInvocationResult(&res) + require.Error(t, err) + + res.State = vmstate.Halt.String() + + // empty stack + _, err = parseContractVersionFromInvocationResult(&res) + require.Error(t, err) + + // invalid item + res.Stack = []stackitem.Item{stackitem.Null{}} + + _, err = parseContractVersionFromInvocationResult(&res) + require.Error(t, err) + + // correct + ver := contractVersion{1, 2, 3} + i := new(big.Int) + + res.Stack = []stackitem.Item{stackitem.NewBigInteger(i.SetUint64(ver.toUint64()))} + + // overflow uint64 + i.SetUint64(math.MaxUint64).Add(i, big.NewInt(1)) + + _, err = parseContractVersionFromInvocationResult(&res) + require.Error(t, err) +} + +type testRPCInvoker struct { + invoker.RPCInvoke + tb testing.TB + exec *neotest.Executor +} + +func newTestRPCInvoker(tb testing.TB, exec *neotest.Executor) *testRPCInvoker { + return &testRPCInvoker{ + tb: tb, + exec: exec, + } +} + +func (x *testRPCInvoker) InvokeScript(script []byte, _ []transaction.Signer) (*result.Invoke, error) { + tx := transaction.New(script, 0) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = x.exec.Chain.BlockHeight() + 1 + tx.Signers = []transaction.Signer{{Account: x.exec.Committee.ScriptHash()}} + + b := x.exec.NewUnsignedBlock(x.tb, tx) + ic, err := x.exec.Chain.GetTestVM(trigger.Application, tx, b) + if err != nil { + return nil, err + } + x.tb.Cleanup(ic.Finalize) + + ic.VM.LoadWithFlags(tx.Script, callflag.All) + err = ic.VM.Run() + if err != nil { + return nil, err + } + + return &result.Invoke{ + State: vmstate.Halt.String(), + Stack: ic.VM.Estack().ToArray(), + }, nil +} + +func TestReadContractLocalVersion(t *testing.T) { + const version = 1_002_003 + + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + src := `package foo + const version = ` + strconv.Itoa(version) + ` + func Version() int { + return version + }` + + ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{Name: "Helper"}) + + res, err := readContractLocalVersion(newTestRPCInvoker(t, e), *ctr.NEF, *ctr.Manifest) + require.NoError(t, err) + require.EqualValues(t, version, res.toUint64()) +} From 1c67f9ac412e4fe1ccfd42560abaaad877201ad2 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Sun, 9 Jul 2023 14:14:28 +0400 Subject: [PATCH 8/9] sidechain/deploy: Use Waiter to track pending transactions Signed-off-by: Leonard Lyubich --- pkg/morph/deploy/deploy.go | 7 +- pkg/morph/deploy/group.go | 76 +++++++------ pkg/morph/deploy/nns.go | 86 +++++++-------- pkg/morph/deploy/notary.go | 219 ++++++++++++++----------------------- pkg/morph/deploy/util.go | 39 +++++++ 5 files changed, 203 insertions(+), 224 deletions(-) diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go index fe3b7c257a3..ab17b9dcffa 100644 --- a/pkg/morph/deploy/deploy.go +++ b/pkg/morph/deploy/deploy.go @@ -106,6 +106,11 @@ type Prm struct { // // See project documentation for details. func Deploy(ctx context.Context, prm Prm) error { + // 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() + committee, err := prm.Blockchain.GetCommittee() if err != nil { return fmt.Errorf("get Neo committee of the network: %w", err) @@ -198,7 +203,7 @@ func Deploy(ctx context.Context, prm Prm) error { prm.Logger.Info("Notary service successfully enabled for the committee") - onNotaryDepositDeficiency, err := initNotaryDepositDeficiencyHandler(prm.Logger, prm.Blockchain, monitor, prm.LocalAccount) + onNotaryDepositDeficiency, err := initNotaryDepositDeficiencyHandler(ctx, prm.Logger, prm.Blockchain, prm.LocalAccount) if err != nil { return fmt.Errorf("construct action depositing funds to the local account's Notary balance: %w", err) } diff --git a/pkg/morph/deploy/group.go b/pkg/morph/deploy/group.go index d94e51676cc..ed1739628ba 100644 --- a/pkg/morph/deploy/group.go +++ b/pkg/morph/deploy/group.go @@ -39,6 +39,11 @@ type initCommitteeGroupPrm struct { // initCommitteeGroup initializes committee group and returns corresponding private key. func initCommitteeGroup(ctx context.Context, prm initCommitteeGroupPrm) (*keys.PrivateKey, error) { + // 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() + inv := invoker.New(prm.blockchain, nil) const leaderCommitteeIndex = 0 var committeeGroupKey *keys.PrivateKey @@ -108,7 +113,7 @@ upperLoop: } if leaderTick == nil { - leaderTick, err = initShareCommitteeGroupKeyAsLeaderTick(prm, committeeGroupKey) + leaderTick, err = initShareCommitteeGroupKeyAsLeaderTick(ctx, prm, committeeGroupKey) if err != nil { prm.logger.Error("failed to construct action sharing committee group key between committee members as leader, will try again later", zap.Error(err)) @@ -123,7 +128,7 @@ upperLoop: // initShareCommitteeGroupKeyAsLeaderTick returns a function that preserves // context of the committee group key distribution by leading committee member // between calls. -func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committeeGroupKey *keys.PrivateKey) (func(), error) { +func initShareCommitteeGroupKeyAsLeaderTick(ctx context.Context, prm initCommitteeGroupPrm, committeeGroupKey *keys.PrivateKey) (func(), error) { localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) @@ -132,7 +137,8 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee invkr := invoker.New(prm.blockchain, nil) // multi-tick context - mDomainsToVubs := make(map[string][2]uint32) // 1st - register, 2nd - addRecord + mDomainsToRegisterTxs := make(map[string]*transactionGroupMonitor, len(prm.committee)) + mDomainsToSetRecordTxs := make(map[string]*transactionGroupMonitor, len(prm.committee)) return func() { prm.logger.Info("distributing committee group key between committee members using NNS...") @@ -148,58 +154,52 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") - vubs, ok := mDomainsToVubs[domain] - if ok && vubs[0] > 0 { - l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= vubs[0] { - l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[0])) - return + txMonitor, ok := mDomainsToRegisterTxs[domain] + if ok { + if txMonitor.isPending() { + l.Info("previously sent transaction registering NNS domain is still pending, will wait for the outcome") + continue } - - l.Info("previously sent transaction registering NNS domain expired without side-effect") + } else { + txMonitor = newTransactionGroupMonitor(localActor) + mDomainsToRegisterTxs[domain] = txMonitor } l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + txID, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, domain, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { - vubs[0] = 0 - mDomainsToVubs[domain] = vubs if isErrNotEnoughGAS(err) { l.Info("not enough GAS to register domain in the NNS, will try again later") } else { l.Error("failed to send transaction registering domain in the NNS, will try again later", zap.Error(err)) } - return + continue } - vubs[0] = vub - mDomainsToVubs[domain] = vubs + l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + txMonitor.trackPendingTransactionsAsync(ctx, vub, txID) continue } else if !errors.Is(err, errMissingDomainRecord) { l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) - return + continue } l.Info("missing record of the NNS domain, needed to be set") - vubs, ok := mDomainsToVubs[domain] - if ok && vubs[1] > 0 { - l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= vubs[1] { - l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", vubs[1])) - return + txMonitor, ok := mDomainsToSetRecordTxs[domain] + if ok { + if txMonitor.isPending() { + l.Info("previously sent transaction setting NNS domain record is still pending, will wait for the outcome") + continue } - - l.Info("previously sent transaction setting NNS domain record expired without side-effect") + } else { + txMonitor = newTransactionGroupMonitor(localActor) + mDomainsToSetRecordTxs[domain] = txMonitor } l.Info("sharing encrypted committee group key with the committee member...") @@ -208,28 +208,26 @@ func initShareCommitteeGroupKeyAsLeaderTick(prm initCommitteeGroupPrm, committee if err != nil { l.Error("failed to encrypt committee group key to share with the committee member, will try again later", zap.Error(err)) - return + continue } l.Info("sending new transaction setting domain record in the NNS...") - _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + txID, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domain, int64(nns.TXT), keyCipher) if err != nil { - vubs[1] = 0 - mDomainsToVubs[domain] = vubs if isErrNotEnoughGAS(err) { l.Info("not enough GAS to set NNS domain record, will try again later") } else { l.Error("failed to send transaction setting NNS domain record, will try again later", zap.Error(err)) } - return + continue } - vubs[1] = vub - mDomainsToVubs[domain] = vubs + l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + txMonitor.trackPendingTransactionsAsync(ctx, vub, txID) continue } diff --git a/pkg/morph/deploy/nns.go b/pkg/morph/deploy/nns.go index ec0972c9067..d5825c41eb6 100644 --- a/pkg/morph/deploy/nns.go +++ b/pkg/morph/deploy/nns.go @@ -95,9 +95,19 @@ type deployNNSContractPrm struct { // If contract is missing and deployNNSContractPrm.initCommitteeGroupKey is provided, // initNNSContract attempts to deploy local contract. func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Uint160, err error) { - var managementContract *management.Contract - var sentTxValidUntilBlock uint32 + localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) + if err != nil { + return res, fmt.Errorf("init transaction sender from local account: %w", err) + } + + // 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() + var committeeGroupKey *keys.PrivateKey + txMonitor := newTransactionGroupMonitor(localActor) + managementContract := management.New(localActor) for ; ; prm.monitor.waitForNextBlock(ctx) { select { @@ -141,47 +151,29 @@ func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Ui continue } + setGroupInManifest(&prm.localManifest, prm.localNEF, committeeGroupKey, prm.localAcc.ScriptHash()) + prm.logger.Info("private key of the committee group has been initialized", zap.Stringer("public key", committeeGroupKey.PublicKey())) } - if sentTxValidUntilBlock > 0 { - prm.logger.Info("transaction deploying NNS contract was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= sentTxValidUntilBlock { - prm.logger.Info("previously sent transaction deploying NNS contract may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) - continue - } - - prm.logger.Info("previously sent transaction deploying NNS contract expired without side-effect") + if txMonitor.isPending() { + prm.logger.Info("previously sent transaction updating NNS contract is still pending, will wait for the outcome") + continue } prm.logger.Info("sending new transaction deploying NNS contract...") - if managementContract == nil { - localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) - if err != nil { - prm.logger.Warn("NNS contract is missing on the chain but attempts to deploy are disabled, will try again later") - continue - } - - managementContract = management.New(localActor) - - setGroupInManifest(&prm.localManifest, prm.localNEF, committeeGroupKey, prm.localAcc.ScriptHash()) - } - // just to definitely avoid mutation nefCp := prm.localNEF manifestCp := prm.localManifest - _, vub, err := managementContract.Deploy(&nefCp, &manifestCp, []interface{}{ + txID, vub, err := managementContract.Deploy(&nefCp, &manifestCp, []interface{}{ []interface{}{ []interface{}{domainBootstrap, prm.systemEmail}, []interface{}{domainContractAddresses, prm.systemEmail}, }, }) if err != nil { - sentTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to deploy NNS contract, will try again later") } else { @@ -190,9 +182,11 @@ func initNNSContract(ctx context.Context, prm deployNNSContractPrm) (res util.Ui continue } - sentTxValidUntilBlock = vub + prm.logger.Info("transaction deploying NNS contract has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub), + ) - prm.logger.Info("transaction deploying NNS contract has been successfully sent, will wait for the outcome") + txMonitor.trackPendingTransactionsAsync(ctx, vub, txID) } } @@ -286,7 +280,12 @@ func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { return fmt.Errorf("read version of the local NNS contract: %w", err) } - var updateTxValidUntilBlock uint32 + // 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() + + txMonitor := newTransactionGroupMonitor(committeeActor) for ; ; prm.monitor.waitForNextBlock(ctx) { select { @@ -340,8 +339,6 @@ func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { setGroupInManifest(&prm.localManifest, prm.localNEF, prm.committeeGroupKey, prm.localAcc.ScriptHash()) - var vub uint32 - // we pre-check 'already updated' case via MakeCall in order to not potentially // wait for previously sent transaction to be expired (condition below) and // immediately succeed @@ -354,23 +351,17 @@ func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { prm.logger.Info("NNS contract has already been updated, skip") return nil } - } else { - if updateTxValidUntilBlock > 0 { - prm.logger.Info("transaction updating NNS contract was sent earlier, checking relevance...") - if cur := prm.monitor.currentHeight(); cur <= updateTxValidUntilBlock { - prm.logger.Info("previously sent transaction updating NNS contract may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", updateTxValidUntilBlock)) - continue - } - - prm.logger.Info("previously sent transaction updating NNS contract expired without side-effect") - } - - prm.logger.Info("sending new transaction updating NNS contract...") + prm.logger.Error("failed to make transaction updating NNS contract, will try again later", zap.Error(err)) + continue + } - _, _, vub, err = committeeActor.Notarize(tx, nil) + if txMonitor.isPending() { + prm.logger.Info("previously sent notary request updating NNS contract is still pending, will wait for the outcome") + continue } + + mainTxID, fallbackTxID, vub, err := committeeActor.Notarize(tx, nil) if err != nil { lackOfGAS := isErrNotEnoughGAS(err) // here lackOfGAS=true always means lack of Notary balance and not related to @@ -393,8 +384,9 @@ func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { continue } - updateTxValidUntilBlock = vub + prm.logger.Info("notary request updating NNS contract has been successfully sent, will wait for the outcome", + zap.Stringer("main tx", mainTxID), zap.Stringer("fallback tx", fallbackTxID), zap.Uint32("vub", vub)) - prm.logger.Info("transaction updating NNS contract has been successfully sent, will wait for the outcome") + txMonitor.trackPendingTransactionsAsync(ctx, vub, mainTxID, fallbackTxID) } } diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go index 8e28dafb6c8..960942d1acd 100644 --- a/pkg/morph/deploy/notary.go +++ b/pkg/morph/deploy/notary.go @@ -53,13 +53,18 @@ type enableNotaryPrm struct { // enableNotary makes Notary service ready-to-go for the committee members. func enableNotary(ctx context.Context, prm enableNotaryPrm) error { + // 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() + var tick func() var err error if len(prm.committee) == 1 { prm.logger.Info("committee is single-acc, no multi-signature needed for Notary role designation") - tick, err = initDesignateNotaryRoleToLocalAccountTick(prm) + tick, err = initDesignateNotaryRoleToLocalAccountTick(ctx, prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the local account: %w", err) } @@ -67,12 +72,12 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { prm.logger.Info("committee is multi-acc, multi-signature is needed for Notary role designation") if prm.localAccCommitteeIndex == 0 { - tick, err = initDesignateNotaryRoleAsLeaderTick(prm) + tick, err = initDesignateNotaryRoleAsLeaderTick(ctx, prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the multi-acc committee as leader: %w", err) } } else { - tick, err = initDesignateNotaryRoleAsSignerTick(prm) + tick, err = initDesignateNotaryRoleAsSignerTick(ctx, prm) if err != nil { return fmt.Errorf("construct action designating Notary role to the multi-acc committee as signer: %w", err) } @@ -118,7 +123,7 @@ func enableNotary(ctx context.Context, prm enableNotaryPrm) error { // initDesignateNotaryRoleToLocalAccountTick returns a function that preserves // context of the Notary role designation to the local account between calls. -func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), error) { +func initDesignateNotaryRoleToLocalAccountTick(ctx context.Context, prm enableNotaryPrm) (func(), error) { localActor, err := actor.NewSimple(prm.blockchain, prm.localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) @@ -127,33 +132,20 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), err roleContract := rolemgmt.New(localActor) // multi-tick context - var sentTxValidUntilBlock uint32 + txMonitor := newTransactionGroupMonitor(localActor) return func() { - if sentTxValidUntilBlock > 0 && sentTxValidUntilBlock <= prm.monitor.currentHeight() { - prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome") + if txMonitor.isPending() { + prm.logger.Info("previously sent transaction designating Notary role to the local account is still pending, will wait for the outcome") return } - if sentTxValidUntilBlock > 0 { - prm.logger.Info("transaction designating Notary role to the local account was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= sentTxValidUntilBlock { - prm.logger.Info("previously sent transaction designating Notary role to the local account may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", sentTxValidUntilBlock)) - return - } - - prm.logger.Info("previously sent transaction designating Notary role to the local account expired without side-effect") - } - prm.logger.Info("sending new transaction designating Notary role to the local account...") var err error - _, vub, err := roleContract.DesignateAsRole(noderoles.P2PNotary, keys.PublicKeys{prm.localAcc.PublicKey()}) + txID, vub, err := roleContract.DesignateAsRole(noderoles.P2PNotary, keys.PublicKeys{prm.localAcc.PublicKey()}) if err != nil { - sentTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to designate Notary role to the local account, will try again later") } else { @@ -162,9 +154,10 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), err return } - sentTxValidUntilBlock = vub + prm.logger.Info("transaction designating Notary role to the local account has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - prm.logger.Info("transaction designating Notary role to the local account has been successfully sent, will wait for the outcome") + txMonitor.trackPendingTransactionsAsync(ctx, vub, txID) }, nil } @@ -172,7 +165,7 @@ func initDesignateNotaryRoleToLocalAccountTick(prm enableNotaryPrm) (func(), err // of the Notary role designation to the multi-acc committee between calls. The // operation is performed by the leading committee member which is assigned to // collect signatures for the corresponding transaction. -func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { +func initDesignateNotaryRoleAsLeaderTick(ctx context.Context, prm enableNotaryPrm) (func(), error) { committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) @@ -212,21 +205,22 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { roleContract := rolemgmt.New(committeeActor) // multi-tick context - var registerDomainTxValidUntilBlock uint32 - var setDomainRecordTxValidUntilBlock uint32 var tx *transaction.Transaction var mCommitteeIndexToSignature map[int][]byte - var designateRoleTxValidUntilBlock uint32 var txFullySigned bool + var triedDesignateRoleTx bool + registerDomainTxMonitor := newTransactionGroupMonitor(localActor) + setDomainRecordTxMonitor := newTransactionGroupMonitor(localActor) + designateRoleTxMonitor := newTransactionGroupMonitor(localActor) resetTx := func() { tx = nil - setDomainRecordTxValidUntilBlock = 0 for k := range mCommitteeIndexToSignature { delete(mCommitteeIndexToSignature, k) } - designateRoleTxValidUntilBlock = 0 txFullySigned = false + setDomainRecordTxMonitor.reset() + designateRoleTxMonitor.reset() } return func() { @@ -267,16 +261,16 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { l.Info("sending new transaction setting domain record in the NNS...") + var txID util.Uint256 var vub uint32 if recordExists { - _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + txID, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, domainDesignateNotaryTx, int64(nns.TXT), 0, strSharedTxData) } else { - _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + txID, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domainDesignateNotaryTx, int64(nns.TXT), strSharedTxData) } if err != nil { - setDomainRecordTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to set NNS domain record, will try again later") } else { @@ -285,9 +279,10 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return } - setDomainRecordTxValidUntilBlock = vub + l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + setDomainRecordTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) } strSharedTxData, err := lookupNNSDomainRecord(invkr, prm.nnsOnChainAddress, domainDesignateNotaryTx) @@ -295,24 +290,16 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") - if registerDomainTxValidUntilBlock > 0 { - l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { - l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction registering NNS domain expired without side-effect") + if registerDomainTxMonitor.isPending() { + prm.logger.Info("previously sent transaction registering NNS domain is still pending, will wait for the outcome") + return } l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + txID, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, domainDesignateNotaryTx, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { - registerDomainTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to register domain in the NNS, will try again later") } else { @@ -321,10 +308,10 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return } - registerDomainTxValidUntilBlock = vub - l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + registerDomainTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) + return } else if !errors.Is(err, errMissingDomainRecord) { l.Error("failed to lookup NNS domain record, will try again later", zap.Error(err)) @@ -333,16 +320,9 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { l.Info("missing record of the NNS domain, needed to be set") - if setDomainRecordTxValidUntilBlock > 0 { - l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { - l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction setting NNS domain record expired without side-effect") + if setDomainRecordTxMonitor.isPending() { + prm.logger.Info("previously sent transaction setting NNS domain record is still pending, will wait for the outcome") + return } generateAndShareTxData(false) @@ -485,15 +465,10 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { prm.logger.Info("gathered enough signatures of the transaction designating Notary role to the committee") - if designateRoleTxValidUntilBlock > 0 { - prm.logger.Info("transaction designating Notary role to the committee was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= designateRoleTxValidUntilBlock { - prm.logger.Info("previously sent transaction designating Notary role to the committee may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", designateRoleTxValidUntilBlock)) - return - } - + if registerDomainTxMonitor.isPending() { + prm.logger.Info("previously sent transaction designating Notary role to the committee is still pending, will wait for the outcome") + return + } else if triedDesignateRoleTx { prm.logger.Info("previously sent transaction designating Notary role to the committee expired without side-effect, will recreate") generateAndShareTxData(true) return @@ -525,9 +500,8 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { prm.logger.Info("sending the transaction designating Notary role to the committee...") - _, vub, err := localActor.Send(tx) + txID, vub, err := localActor.Send(tx) if err != nil { - designateRoleTxValidUntilBlock = 0 switch { default: prm.logger.Error("failed to send transaction designating Notary role to the committee, will try again later", @@ -542,9 +516,11 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { return } - designateRoleTxValidUntilBlock = vub + prm.logger.Info("transaction designating Notary role to the committee has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - prm.logger.Info("transaction designating Notary role to the committee has been successfully sent, will wait for the outcome") + triedDesignateRoleTx = true + designateRoleTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) }, nil } @@ -552,7 +528,7 @@ func initDesignateNotaryRoleAsLeaderTick(prm enableNotaryPrm) (func(), error) { // of the Notary role designation to the multi-acc committee between calls. The // operation is performed by the non-leading committee member which is assigned to // sign transaction submitted by the leader. -func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { +func initDesignateNotaryRoleAsSignerTick(ctx context.Context, prm enableNotaryPrm) (func(), error) { committeeMultiSigM := smartcontract.GetMajorityHonestNodeCount(len(prm.committee)) committeeMultiSigAcc := wallet.NewAccountFromPrivateKey(prm.localAcc.PrivateKey()) @@ -593,12 +569,12 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { // multi-tick context var tx *transaction.Transaction - var registerDomainTxValidUntilBlock uint32 - var setDomainRecordTxValidUntilBlock uint32 + registerDomainTxMonitor := newTransactionGroupMonitor(localActor) + setDomainRecordTxMonitor := newTransactionGroupMonitor(localActor) resetTx := func() { tx = nil - setDomainRecordTxValidUntilBlock = 0 + setDomainRecordTxMonitor.reset() } return func() { @@ -667,24 +643,16 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { if errors.Is(err, errMissingDomain) { l.Info("NNS domain is missing, registration is needed") - if registerDomainTxValidUntilBlock > 0 { - l.Info("transaction registering NNS domain was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= registerDomainTxValidUntilBlock { - l.Info("previously sent transaction registering NNS domain may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", registerDomainTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction registering NNS domain expired without side-effect") + if registerDomainTxMonitor.isPending() { + prm.logger.Info("previously sent transaction registering NNS domain is still pending, will wait for the outcome") + return } l.Info("sending new transaction registering domain in the NNS...") - _, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, + txID, vub, err := localActor.SendCall(prm.nnsOnChainAddress, methodNNSRegister, domain, localActor.Sender(), prm.systemEmail, nnsRefresh, nnsRetry, nnsExpire, nnsMinimum) if err != nil { - registerDomainTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to register domain in the NNS, will try again later") } else { @@ -693,9 +661,10 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { return } - registerDomainTxValidUntilBlock = vub + l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - l.Info("transaction registering domain in the NNS has been successfully sent, will wait for the outcome") + registerDomainTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) return } else if !errors.Is(err, errMissingDomainRecord) { @@ -705,16 +674,9 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { l.Info("missing record of the NNS domain, needed to be set") - if setDomainRecordTxValidUntilBlock > 0 { - l.Info("transaction setting NNS domain record was sent earlier, checking relevance...") - - if cur := prm.monitor.currentHeight(); cur <= setDomainRecordTxValidUntilBlock { - l.Info("previously sent transaction setting NNS domain record may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", setDomainRecordTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction setting NNS domain record expired without side-effect") + if setDomainRecordTxMonitor.isPending() { + prm.logger.Info("previously sent transaction setting NNS domain record is still pending, will wait for the outcome") + return } needReset = true @@ -751,16 +713,16 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { l.Info("sending new transaction setting domain record in the NNS...") + var txID util.Uint256 var vub uint32 if recordExists { - _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, + txID, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSSetRecord, domain, int64(nns.TXT), 0, rec) } else { - _, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, + txID, vub, err = localActor.SendCall(prm.nnsOnChainAddress, methodNNSAddRecord, domain, int64(nns.TXT), rec) } if err != nil { - setDomainRecordTxValidUntilBlock = 0 if isErrNotEnoughGAS(err) { prm.logger.Info("not enough GAS to set NNS domain record, will try again later") } else { @@ -769,10 +731,10 @@ func initDesignateNotaryRoleAsSignerTick(prm enableNotaryPrm) (func(), error) { return } - setDomainRecordTxValidUntilBlock = vub - l.Info("transaction setting NNS domain record has been successfully sent, will wait for the outcome") + setDomainRecordTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) + return } }, nil @@ -929,7 +891,7 @@ var singleNotaryDepositAmount = big.NewInt(1_0000_0000) // 1 GAS // constructs notaryDepositDeficiencyHandler working with the specified // Blockchain and GAS/Notary balance of the given account. -func initNotaryDepositDeficiencyHandler(l *zap.Logger, b Blockchain, monitor *blockchainMonitor, localAcc *wallet.Account) (notaryDepositDeficiencyHandler, error) { +func initNotaryDepositDeficiencyHandler(ctx context.Context, l *zap.Logger, b Blockchain, localAcc *wallet.Account) (notaryDepositDeficiencyHandler, error) { localActor, err := actor.NewSimple(b, localAcc) if err != nil { return nil, fmt.Errorf("init transaction sender from local account: %w", err) @@ -940,8 +902,8 @@ func initNotaryDepositDeficiencyHandler(l *zap.Logger, b Blockchain, monitor *bl localAccID := localAcc.ScriptHash() // multi-tick context - var transferTxValidUntilBlock uint32 - var expirationTxValidUntilBlock uint32 + transferTxMonitor := newTransactionGroupMonitor(localActor) + expirationTxMonitor := newTransactionGroupMonitor(localActor) return func(lackOfGAS bool) { notaryBalance, err := notaryContract.BalanceOf(localAccID) @@ -976,48 +938,31 @@ func initNotaryDepositDeficiencyHandler(l *zap.Logger, b Blockchain, monitor *bl if !lackOfGAS { // deposit expired if new(big.Int).Mul(notaryBalance, big.NewInt(refillProportion)).Cmp(singleNotaryDepositAmount) >= 0 { - if expirationTxValidUntilBlock > 0 { - l.Info("transaction increasing expiration time of the Notary deposit was sent earlier, checking relevance...") - - if cur := monitor.currentHeight(); cur <= expirationTxValidUntilBlock { - l.Info("previously sent transaction increasing expiration time of the Notary deposit may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", expirationTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction increasing expiration time of the Notary deposit expired without side-effect ") + if expirationTxMonitor.isPending() { + l.Info("previously sent transaction increasing expiration time of the Notary deposit is still pending, will wait for the outcome") + return } l.Info("sending new transaction increasing expiration time of the Notary deposit...", zap.Uint32("till", till)) - _, vub, err := notaryContract.LockDepositUntil(localAccID, till) + txID, vub, err := notaryContract.LockDepositUntil(localAccID, till) if err != nil { l.Error("failed to send transaction increasing expiration time of the Notary deposit, will try again later", zap.Error(err)) return } - expirationTxValidUntilBlock = vub + l.Info("transaction increasing expiration time of the Notary deposit has been successfully sent, will wait for the outcome", + zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - l.Info("transaction increasing expiration time of the Notary deposit has been successfully sent, will wait for the outcome") + expirationTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) return } } - if transferTxValidUntilBlock > 0 { - l.Info("transaction transferring local account's GAS to the Notary contract was sent earlier, checking relevance...") - - // for simplicity, we track ValidUntilBlock. In this particular case, it'd be - // more efficient to monitor a transaction by ID, because side effect is - // inconsistent (funds can be spent in background). - - if cur := monitor.currentHeight(); cur <= transferTxValidUntilBlock { - l.Info("previously sent transaction transferring local account's GAS to the Notary contract may still be relevant, will wait for the outcome", - zap.Uint32("current height", cur), zap.Uint32("retry after height", transferTxValidUntilBlock)) - return - } - - l.Info("previously sent transaction transferring local account's GAS to the Notary contract expired without side-effect") + if transferTxMonitor.isPending() { + l.Info("previously sent transaction local account's GAS to the Notary contract is still pending, will wait for the outcome") + return } needAtLeast := new(big.Int).Mul(singleNotaryDepositAmount, big.NewInt(gasProportion)) @@ -1037,15 +982,15 @@ func initNotaryDepositDeficiencyHandler(l *zap.Logger, b Blockchain, monitor *bl // nep17.TokenWriter.Transfer doesn't support notary.OnNEP17PaymentData // directly, so split the args // Track https://github.com/nspcc-dev/neofs-node/issues/2429 - _, vub, err := gasContract.Transfer(localAccID, notary.Hash, singleNotaryDepositAmount, []interface{}{transferData.Account, transferData.Till}) + txID, vub, err := gasContract.Transfer(localAccID, notary.Hash, singleNotaryDepositAmount, []interface{}{transferData.Account, transferData.Till}) if err != nil { l.Error("failed to send transaction transferring local account's GAS to the Notary contract, will try again later", zap.Error(err)) return } - transferTxValidUntilBlock = vub - l.Info("transaction transferring local account's GAS to the Notary contract has been successfully sent, will wait for the outcome") + + transferTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) }, nil } diff --git a/pkg/morph/deploy/util.go b/pkg/morph/deploy/util.go index fc164a4458c..903d91ad2c0 100644 --- a/pkg/morph/deploy/util.go +++ b/pkg/morph/deploy/util.go @@ -284,3 +284,42 @@ func readContractLocalVersion(rpc invoker.RPCInvoke, localNEF nef.File, localMan return parseContractVersionFromInvocationResult(res) } + +type transactionGroupWaiter interface { + WaitAny(ctx context.Context, vub uint32, hashes ...util.Uint256) (*state.AppExecResult, error) +} + +type transactionGroupMonitor struct { + waiter transactionGroupWaiter + pending atomic.Bool +} + +func newTransactionGroupMonitor(w transactionGroupWaiter) *transactionGroupMonitor { + return &transactionGroupMonitor{ + waiter: w, + } +} + +func (x *transactionGroupMonitor) reset() { + x.pending.Store(false) +} + +func (x *transactionGroupMonitor) isPending() bool { + return x.pending.Load() +} + +func (x *transactionGroupMonitor) trackPendingTransactionsAsync(ctx context.Context, vub uint32, txs ...util.Uint256) { + if len(txs) == 0 { + panic("missing transactions") + } + + x.pending.Store(true) + + waitCtx, cancel := context.WithCancel(ctx) + + go func() { + _, _ = x.waiter.WaitAny(waitCtx, vub, txs...) + x.reset() + cancel() + }() +} From 29877e96c59051ea739816a501bd57e0b01892fa Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Sun, 9 Jul 2023 21:08:15 +0400 Subject: [PATCH 9/9] sidechain/deploy: Use separate autonomous routine for Notary deposit Signed-off-by: Leonard Lyubich --- pkg/morph/deploy/deploy.go | 19 +++-- pkg/morph/deploy/nns.go | 23 ++---- pkg/morph/deploy/notary.go | 143 +++++++++++++------------------------ pkg/morph/deploy/util.go | 12 ++-- 4 files changed, 68 insertions(+), 129 deletions(-) diff --git a/pkg/morph/deploy/deploy.go b/pkg/morph/deploy/deploy.go index ab17b9dcffa..09328a5811e 100644 --- a/pkg/morph/deploy/deploy.go +++ b/pkg/morph/deploy/deploy.go @@ -134,7 +134,9 @@ func Deploy(ctx context.Context, prm Prm) error { return errors.New("local account does not belong to any Neo committee member") } - monitor, err := newBlockchainMonitor(prm.Logger, prm.Blockchain) + chNewBlock := make(chan struct{}, 1) + + monitor, err := newBlockchainMonitor(prm.Logger, prm.Blockchain, chNewBlock) if err != nil { return fmt.Errorf("init blockchain monitor: %w", err) } @@ -203,17 +205,13 @@ func Deploy(ctx context.Context, prm Prm) error { prm.Logger.Info("Notary service successfully enabled for the committee") - onNotaryDepositDeficiency, err := initNotaryDepositDeficiencyHandler(ctx, prm.Logger, prm.Blockchain, prm.LocalAccount) - if err != nil { - return fmt.Errorf("construct action depositing funds to the local account's Notary balance: %w", err) - } + go autoReplenishNotaryBalance(ctx, prm.Logger, prm.Blockchain, prm.LocalAccount, chNewBlock) err = listenCommitteeNotaryRequests(ctx, listenCommitteeNotaryRequestsPrm{ - logger: prm.Logger, - blockchain: prm.Blockchain, - localAcc: prm.LocalAccount, - committee: committee, - onNotaryDepositDeficiency: onNotaryDepositDeficiency, + logger: prm.Logger, + blockchain: prm.Blockchain, + localAcc: prm.LocalAccount, + committee: committee, }) if err != nil { return fmt.Errorf("start listener of committee notary requests: %w", err) @@ -251,7 +249,6 @@ func Deploy(ctx context.Context, prm Prm) error { committee: committee, committeeGroupKey: committeeGroupKey, buildVersionedExtraUpdateArgs: noExtraUpdateArgs, - onNotaryDepositDeficiency: onNotaryDepositDeficiency, }) if err != nil { return fmt.Errorf("update NNS contract on the chain: %w", err) diff --git a/pkg/morph/deploy/nns.go b/pkg/morph/deploy/nns.go index d5825c41eb6..66f9bec0025 100644 --- a/pkg/morph/deploy/nns.go +++ b/pkg/morph/deploy/nns.go @@ -244,8 +244,6 @@ type updateNNSContractPrm struct { // contract. If returns both nil, no data is passed (noExtraUpdateArgs may be // used). buildVersionedExtraUpdateArgs func(versionOnChain contractVersion) ([]interface{}, error) - - onNotaryDepositDeficiency notaryDepositDeficiencyHandler } // updateNNSContract synchronizes on-chain NNS contract (its presence is a @@ -363,24 +361,11 @@ func updateNNSContract(ctx context.Context, prm updateNNSContractPrm) error { mainTxID, fallbackTxID, vub, err := committeeActor.Notarize(tx, nil) if err != nil { - lackOfGAS := isErrNotEnoughGAS(err) - // here lackOfGAS=true always means lack of Notary balance and not related to - // the main transaction itself - if !lackOfGAS { - if !isErrNotaryDepositExpires(err) { - prm.logger.Error("failed to send transaction deploying NNS contract, will try again later", zap.Error(err)) - continue - } + if isErrNotEnoughGAS(err) { + prm.logger.Info("insufficient Notary balance to send new Notary request updating NNS contract, skip") + } else { + prm.logger.Error("failed to send new Notary request updating NNS contract, skip", zap.Error(err)) } - - // same approach with in-place deposit is going to be used in other functions. - // Consider replacement with background process (e.g. blockchainMonitor - // internal) which periodically checks Notary balance and updates it when, for - // example, balance goes lower than 20% of desired amount or expires soon. With - // this approach functions like current will not try to make a deposit, but - // simply wait until it becomes enough. - prm.onNotaryDepositDeficiency(lackOfGAS) - continue } diff --git a/pkg/morph/deploy/notary.go b/pkg/morph/deploy/notary.go index 960942d1acd..64c4e7cb766 100644 --- a/pkg/morph/deploy/notary.go +++ b/pkg/morph/deploy/notary.go @@ -21,6 +21,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" @@ -871,51 +872,56 @@ func newCommitteeNotaryActor(b Blockchain, localAcc *wallet.Account, committee k }, localAcc) } -// notaryDepositDeficiencyHandler is a function returned by initNotaryDepositDeficiencyHandler. -// True argument is passed when there is not enough GAS on local account's -// balance in the Notary contract, false - when local account's Notary deposit -// expires before particular fallback transaction. -// -// The function is intended to be called multiple times on each deposit problem -// encounter. On each call, It attempts to fix Notary deposit problem without -// waiting for success. Caller should by default wait for the problem to be -// fixed, and if not, retry. -// -// notaryDepositDeficiencyHandler must not be called from multiple routines in -// parallel. -type notaryDepositDeficiencyHandler = func(lackOfGAS bool) - // Amount of GAS for the single local account's GAS->Notary transfer. Relatively // small value for fallback transactions' fees. var singleNotaryDepositAmount = big.NewInt(1_0000_0000) // 1 GAS -// constructs notaryDepositDeficiencyHandler working with the specified -// Blockchain and GAS/Notary balance of the given account. -func initNotaryDepositDeficiencyHandler(ctx context.Context, l *zap.Logger, b Blockchain, localAcc *wallet.Account) (notaryDepositDeficiencyHandler, error) { - localActor, err := actor.NewSimple(b, localAcc) - if err != nil { - return nil, fmt.Errorf("init transaction sender from local account: %w", err) - } +func autoReplenishNotaryBalance(ctx context.Context, l *zap.Logger, b Blockchain, localAcc *wallet.Account, chTrigger <-chan struct{}) { + l.Info("tracking Notary balance for auto-replenishment...") - notaryContract := notary.New(localActor) - gasContract := gas.New(localActor) + var err error + var localActor *actor.Actor + var notaryContract *notary.Contract + var gasContract *nep17.Token + var txMonitor *transactionGroupMonitor localAccID := localAcc.ScriptHash() - // multi-tick context - transferTxMonitor := newTransactionGroupMonitor(localActor) - expirationTxMonitor := newTransactionGroupMonitor(localActor) + for { + select { + case <-ctx.Done(): + l.Info("Notary balance tracker stopped by context", zap.Error(ctx.Err())) + return + case _, ok := <-chTrigger: + if !ok { + l.Info("Notary balance tracker stopped by closed block channel") + return + } + } + + if localActor == nil { + localActor, err = actor.NewSimple(b, localAcc) + if err != nil { + l.Error("failed to init transaction sender from local account, will try again later", zap.Error(err)) + continue + } + + notaryContract = notary.New(localActor) + gasContract = gas.New(localActor) + txMonitor = newTransactionGroupMonitor(localActor) + } - return func(lackOfGAS bool) { notaryBalance, err := notaryContract.BalanceOf(localAccID) if err != nil { l.Error("failed to read Notary balance of the local account, will try again later", zap.Error(err)) - return + continue } - gasBalance, err := gasContract.BalanceOf(localAccID) - if err != nil { - l.Error("failed to read GAS token balance of the local account, will try again later", zap.Error(err)) - return + // deposit when balance falls below 1/5 of the single deposit amount + const refillProportion = 5 + + if new(big.Int).Mul(notaryBalance, big.NewInt(refillProportion)).Cmp(singleNotaryDepositAmount) >= 0 { + l.Info("enough funds on the notary balance, deposit is not needed", zap.Stringer("balance", notaryBalance)) + continue } // simple deposit scheme: transfer 1GAS (at most 2% of GAS token balance) for @@ -925,56 +931,14 @@ func initNotaryDepositDeficiencyHandler(ctx context.Context, l *zap.Logger, b Bl // If we encounter deposit expiration and current Notary balance >=20% of single // transfer, we just increase the expiration time of the deposit, otherwise, we // make transfer. - - const ( - // GAS:Notary proportion, see scheme above - gasProportion = 50 - // even there is no lack of GAS at the moment, when the balance falls below 1/5 - // of the supported value - replenish - refillProportion = 5 - // for simplicity, we just make Notary deposit "infinite" not to prolong - till = math.MaxUint32 - ) - - if !lackOfGAS { // deposit expired - if new(big.Int).Mul(notaryBalance, big.NewInt(refillProportion)).Cmp(singleNotaryDepositAmount) >= 0 { - if expirationTxMonitor.isPending() { - l.Info("previously sent transaction increasing expiration time of the Notary deposit is still pending, will wait for the outcome") - return - } - - l.Info("sending new transaction increasing expiration time of the Notary deposit...", zap.Uint32("till", till)) - - txID, vub, err := notaryContract.LockDepositUntil(localAccID, till) - if err != nil { - l.Error("failed to send transaction increasing expiration time of the Notary deposit, will try again later", zap.Error(err)) - return - } - - l.Info("transaction increasing expiration time of the Notary deposit has been successfully sent, will wait for the outcome", - zap.Stringer("tx", txID), zap.Uint32("vub", vub)) - - expirationTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) - - return - } - } - - if transferTxMonitor.isPending() { - l.Info("previously sent transaction local account's GAS to the Notary contract is still pending, will wait for the outcome") - return - } - - needAtLeast := new(big.Int).Mul(singleNotaryDepositAmount, big.NewInt(gasProportion)) - if gasBalance.Cmp(needAtLeast) < 0 { - l.Info("minimum threshold for GAS transfer from local account to the Notary contract not reached, will wait for replenishment", - zap.Stringer("need at least", needAtLeast), zap.Stringer("have", gasBalance)) - return + if txMonitor.isPending() { + l.Info("previously sent transaction transferring local account's GAS to the Notary contract is still pending, will wait for the outcome") + continue } var transferData notary.OnNEP17PaymentData transferData.Account = &localAccID - transferData.Till = till + transferData.Till = math.MaxUint32 // deposit "forever" so we don't have to renew l.Info("sending new transaction transferring local account's GAS to the Notary contract...", zap.Stringer("amount", singleNotaryDepositAmount), zap.Uint32("till", transferData.Till)) @@ -985,13 +949,13 @@ func initNotaryDepositDeficiencyHandler(ctx context.Context, l *zap.Logger, b Bl txID, vub, err := gasContract.Transfer(localAccID, notary.Hash, singleNotaryDepositAmount, []interface{}{transferData.Account, transferData.Till}) if err != nil { l.Error("failed to send transaction transferring local account's GAS to the Notary contract, will try again later", zap.Error(err)) - return + continue } l.Info("transaction transferring local account's GAS to the Notary contract has been successfully sent, will wait for the outcome") - transferTxMonitor.trackPendingTransactionsAsync(ctx, vub, txID) - }, nil + txMonitor.trackPendingTransactionsAsync(ctx, vub, txID) + } } // listenCommitteeNotaryRequestsPrm groups parameters of listenCommitteeNotaryRequests. @@ -1003,8 +967,6 @@ type listenCommitteeNotaryRequestsPrm struct { localAcc *wallet.Account committee keys.PublicKeys - - onNotaryDepositDeficiency notaryDepositDeficiencyHandler } // listenCommitteeNotaryRequests starts background process listening to incoming @@ -1146,18 +1108,11 @@ func listenCommitteeNotaryRequests(ctx context.Context, prm listenCommitteeNotar _, _, _, err = notaryActor.Notarize(mainTx, nil) if err != nil { - lackOfGAS := isErrNotEnoughGAS(err) - // here lackOfGAS=true always means lack of Notary balance and not related to - // the main transaction itself - if !lackOfGAS { - if !isErrNotaryDepositExpires(err) { - prm.logger.Error("failed to send transaction deploying NNS contract, will try again later", zap.Error(err)) - continue - } + if isErrNotEnoughGAS(err) { + prm.logger.Info("insufficient Notary balance to send new Notary request with the main transaction signed by the local account, skip") + } else { + prm.logger.Error("failed to send new Notary request with the main transaction signed by the local account, skip", zap.Error(err)) } - - prm.onNotaryDepositDeficiency(lackOfGAS) - continue } diff --git a/pkg/morph/deploy/util.go b/pkg/morph/deploy/util.go index 903d91ad2c0..30d37d4e865 100644 --- a/pkg/morph/deploy/util.go +++ b/pkg/morph/deploy/util.go @@ -44,10 +44,6 @@ func isErrInvalidTransaction(err error) bool { return errors.Is(err, neorpc.ErrValidationFailed) } -func isErrNotaryDepositExpires(err error) bool { - return strings.Contains(err.Error(), "fallback transaction is valid after deposit is unlocked") -} - func isErrContractAlreadyUpdated(err error) bool { return strings.Contains(err.Error(), common.ErrAlreadyUpdated) } @@ -92,7 +88,7 @@ type blockchainMonitor struct { // newBlockchainMonitor constructs and runs monitor for the given Blockchain. // Resulting blockchainMonitor must be stopped when no longer needed. -func newBlockchainMonitor(l *zap.Logger, b Blockchain) (*blockchainMonitor, error) { +func newBlockchainMonitor(l *zap.Logger, b Blockchain, chNewBlock chan<- struct{}) (*blockchainMonitor, error) { ver, err := b.GetVersion() if err != nil { return nil, fmt.Errorf("request Neo protocol configuration: %w", err) @@ -124,12 +120,18 @@ func newBlockchainMonitor(l *zap.Logger, b Blockchain) (*blockchainMonitor, erro for { b, ok := <-blockCh if !ok { + close(chNewBlock) l.Info("listening to new blocks stopped") return } res.height.Store(b.Index) + select { + case chNewBlock <- struct{}{}: + default: + } + l.Info("new block arrived", zap.Uint32("height", b.Index)) } }()