diff --git a/cmd/frostfs-node/netmap.go b/cmd/frostfs-node/netmap.go index ebe152e47..347121e56 100644 --- a/cmd/frostfs-node/netmap.go +++ b/cmd/frostfs-node/netmap.go @@ -418,7 +418,8 @@ func (c *cfg) updateNetMapState(stateSetter func(*nmClient.UpdatePeerPrm)) error prm.SetKey(c.key.PublicKey().Bytes()) stateSetter(&prm) - return c.cfgNetmap.wrapper.UpdatePeerState(prm) + _, err := c.cfgNetmap.wrapper.UpdatePeerState(prm) + return err } type netInfo struct { diff --git a/pkg/innerring/processors/netmap/handlers_test.go b/pkg/innerring/processors/netmap/handlers_test.go index fa87cff92..164ee41da 100644 --- a/pkg/innerring/processors/netmap/handlers_test.go +++ b/pkg/innerring/processors/netmap/handlers_test.go @@ -399,7 +399,7 @@ func (c *testNetmapClient) NetMap() (*netmap.NetMap, error) { return c.netmap, nil } -func (c *testNetmapClient) NewEpoch(epoch uint64, force bool) error { +func (c *testNetmapClient) NewEpoch(epoch uint64) error { c.newEpochs = append(c.newEpochs, epoch) return nil } diff --git a/pkg/innerring/processors/netmap/process_epoch.go b/pkg/innerring/processors/netmap/process_epoch.go index 01bfbae67..f3cb9837f 100644 --- a/pkg/innerring/processors/netmap/process_epoch.go +++ b/pkg/innerring/processors/netmap/process_epoch.go @@ -77,7 +77,7 @@ func (np *Processor) processNewEpochTick() bool { nextEpoch := np.epochState.EpochCounter() + 1 np.log.Debug(logs.NetmapNextEpoch, zap.Uint64("value", nextEpoch)) - err := np.netmapClient.NewEpoch(nextEpoch, false) + err := np.netmapClient.NewEpoch(nextEpoch) if err != nil { np.log.Error(logs.NetmapCantInvokeNetmapNewEpoch, zap.Error(err)) return false diff --git a/pkg/innerring/processors/netmap/processor.go b/pkg/innerring/processors/netmap/processor.go index 6b8a24a62..e8fb8721b 100644 --- a/pkg/innerring/processors/netmap/processor.go +++ b/pkg/innerring/processors/netmap/processor.go @@ -60,7 +60,7 @@ type ( EpochDuration() (uint64, error) MorphTxHeight(h util.Uint256) (res uint32, err error) NetMap() (*netmap.NetMap, error) - NewEpoch(epoch uint64, force bool) error + NewEpoch(epoch uint64) error MorphIsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) MorphNotarySignAndInvokeTX(mainTx *transaction.Transaction) error } diff --git a/pkg/innerring/processors/netmap/wrappers.go b/pkg/innerring/processors/netmap/wrappers.go index f9af703de..618c1fb8f 100644 --- a/pkg/innerring/processors/netmap/wrappers.go +++ b/pkg/innerring/processors/netmap/wrappers.go @@ -19,7 +19,8 @@ type netmapClientWrapper struct { } func (w *netmapClientWrapper) UpdatePeerState(p netmapclient.UpdatePeerPrm) error { - return w.netmapClient.UpdatePeerState(p) + _, err := w.netmapClient.UpdatePeerState(p) + return err } func (w *netmapClientWrapper) MorphNotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...any) error { @@ -43,8 +44,9 @@ func (w *netmapClientWrapper) NetMap() (*netmap.NetMap, error) { return w.netmapClient.NetMap() } -func (w *netmapClientWrapper) NewEpoch(epoch uint64, force bool) error { - return w.netmapClient.NewEpoch(epoch, force) +func (w *netmapClientWrapper) NewEpoch(epoch uint64) error { + _, err := w.netmapClient.NewEpoch(epoch, 0, false) + return err } func (w *netmapClientWrapper) MorphIsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) { diff --git a/pkg/morph/client/container/delete.go b/pkg/morph/client/container/delete.go index c6cf7aa8d..20351b570 100644 --- a/pkg/morph/client/container/delete.go +++ b/pkg/morph/client/container/delete.go @@ -26,7 +26,8 @@ func Delete(c *Client, witness core.RemovalWitness) error { prm.SetToken(tok.Marshal()) } - return c.Delete(prm) + _, err := c.Delete(prm) + return err } // DeletePrm groups parameters of Delete client operation. @@ -62,13 +63,13 @@ func (d *DeletePrm) SetKey(key []byte) { // Delete removes the container from FrostFS system // through Container contract call. // -// Returns any error encountered that caused +// Returns valid until block and any error encountered that caused // the removal to interrupt. // // If TryNotary is provided, calls notary contract. -func (c *Client) Delete(p DeletePrm) error { +func (c *Client) Delete(p DeletePrm) (uint32, error) { if len(p.signature) == 0 && !p.IsControl() { - return errNilArgument + return 0, errNilArgument } prm := client.InvokePrm{} @@ -76,9 +77,9 @@ func (c *Client) Delete(p DeletePrm) error { prm.SetArgs(p.cnr, p.signature, p.key, p.token) prm.InvokePrmOptional = p.InvokePrmOptional - _, err := c.client.Invoke(prm) + res, err := c.client.Invoke(prm) if err != nil { - return fmt.Errorf("could not invoke method (%s): %w", deleteMethod, err) + return 0, fmt.Errorf("could not invoke method (%s): %w", deleteMethod, err) } - return nil + return res.VUB, nil } diff --git a/pkg/morph/client/netmap/new_epoch.go b/pkg/morph/client/netmap/new_epoch.go index 5f9f1ce5c..a596b9991 100644 --- a/pkg/morph/client/netmap/new_epoch.go +++ b/pkg/morph/client/netmap/new_epoch.go @@ -11,14 +11,17 @@ import ( // If `force` is true, this call is normally initiated by a control // service command and uses a control notary transaction internally // to ensure all nodes produce the same transaction with high probability. -func (c *Client) NewEpoch(epoch uint64, force bool) error { +// If vub > 0, vub will be used as valid until block value. +func (c *Client) NewEpoch(epoch uint64, vub uint32, force bool) (uint32, error) { prm := client.InvokePrm{} prm.SetMethod(newEpochMethod) prm.SetArgs(epoch) prm.SetControlTX(force) + prm.SetVUB(vub) - if _, err := c.client.Invoke(prm); err != nil { - return fmt.Errorf("could not invoke method (%s): %w", newEpochMethod, err) + res, err := c.client.Invoke(prm) + if err != nil { + return 0, fmt.Errorf("could not invoke method (%s): %w", newEpochMethod, err) } - return nil + return res.VUB, nil } diff --git a/pkg/morph/client/netmap/peer.go b/pkg/morph/client/netmap/peer.go index 0ce2d3b42..30f51f699 100644 --- a/pkg/morph/client/netmap/peer.go +++ b/pkg/morph/client/netmap/peer.go @@ -43,17 +43,20 @@ func (c *Client) AddPeer(p AddPeerPrm) error { } // ForceRemovePeer marks the given peer as offline via a notary control transaction. -func (c *Client) ForceRemovePeer(nodeInfo netmap.NodeInfo) error { +// If vub > 0, vub will be used as valid until block value. +func (c *Client) ForceRemovePeer(nodeInfo netmap.NodeInfo, vub uint32) (uint32, error) { if !c.client.WithNotary() { - return fmt.Errorf("peer can be forcefully removed only in notary environment") + return 0, fmt.Errorf("peer can be forcefully removed only in notary environment") } prm := UpdatePeerPrm{} prm.SetKey(nodeInfo.PublicKey()) prm.SetControlTX(true) + prm.SetVUB(vub) - if err := c.UpdatePeerState(prm); err != nil { - return fmt.Errorf("updating peer state: %v", err) + vub, err := c.UpdatePeerState(prm) + if err != nil { + return 0, fmt.Errorf("updating peer state: %v", err) } - return nil + return vub, nil } diff --git a/pkg/morph/client/netmap/update_state.go b/pkg/morph/client/netmap/update_state.go index a15459021..7c3a4e8cd 100644 --- a/pkg/morph/client/netmap/update_state.go +++ b/pkg/morph/client/netmap/update_state.go @@ -36,7 +36,7 @@ func (u *UpdatePeerPrm) SetMaintenance() { } // UpdatePeerState changes peer status through Netmap contract call. -func (c *Client) UpdatePeerState(p UpdatePeerPrm) error { +func (c *Client) UpdatePeerState(p UpdatePeerPrm) (uint32, error) { method := updateStateMethod if c.client.WithNotary() && c.client.IsAlpha() { @@ -55,8 +55,9 @@ func (c *Client) UpdatePeerState(p UpdatePeerPrm) error { prm.SetArgs(int64(p.state), p.key) prm.InvokePrmOptional = p.InvokePrmOptional - if _, err := c.client.Invoke(prm); err != nil { - return fmt.Errorf("could not invoke smart contract: %w", err) + res, err := c.client.Invoke(prm) + if err != nil { + return 0, fmt.Errorf("could not invoke smart contract: %w", err) } - return nil + return res.VUB, nil } diff --git a/pkg/morph/client/notary.go b/pkg/morph/client/notary.go index ca3d37c2b..dfdd7c03b 100644 --- a/pkg/morph/client/notary.go +++ b/pkg/morph/client/notary.go @@ -378,7 +378,7 @@ func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce ui // not expected to be signed by the current node. // // Considered to be used by non-IR nodes. -func (c *Client) NotaryInvokeNotAlpha(contract util.Uint160, fee fixedn.Fixed8, method string, args ...any) (uint32, error) { +func (c *Client) NotaryInvokeNotAlpha(contract util.Uint160, fee fixedn.Fixed8, vubP *uint32, method string, args ...any) (uint32, error) { c.switchLock.RLock() defer c.switchLock.RUnlock() @@ -390,7 +390,7 @@ func (c *Client) NotaryInvokeNotAlpha(contract util.Uint160, fee fixedn.Fixed8, return c.Invoke(contract, fee, method, args...) } - return c.notaryInvoke(false, false, contract, rand.Uint32(), nil, method, args...) + return c.notaryInvoke(false, false, contract, rand.Uint32(), vubP, method, args...) } // NotarySignAndInvokeTX signs and sends notary request that was received from diff --git a/pkg/morph/client/static.go b/pkg/morph/client/static.go index 1dea4c4b7..595e11421 100644 --- a/pkg/morph/client/static.go +++ b/pkg/morph/client/static.go @@ -100,6 +100,8 @@ type InvokePrmOptional struct { // It's only used by notary transactions and it affects only the // computation of `validUntilBlock` values. controlTX bool + // vub is used to set custom valid until block value. + vub uint32 } // SetHash sets optional hash of the transaction. @@ -120,6 +122,11 @@ func (i *InvokePrmOptional) IsControl() bool { return i.controlTX } +// SetVUB sets valid until block value. +func (i *InvokePrmOptional) SetVUB(v uint32) { + i.vub = v +} + type InvokeRes struct { VUB uint32 } @@ -136,11 +143,11 @@ type InvokeRes struct { func (s StaticClient) Invoke(prm InvokePrm) (InvokeRes, error) { var res InvokeRes var err error + var vubP *uint32 if s.tryNotary { if s.alpha { var ( nonce uint32 = 1 - vubP *uint32 vub uint32 err error ) @@ -158,11 +165,19 @@ func (s StaticClient) Invoke(prm InvokePrm) (InvokeRes, error) { vubP = &vub } + if prm.vub > 0 { + vubP = &prm.vub + } + res.VUB, err = s.client.NotaryInvoke(s.scScriptHash, s.fee, nonce, vubP, prm.method, prm.args...) return res, err } - res.VUB, err = s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, prm.method, prm.args...) + if prm.vub > 0 { + vubP = &prm.vub + } + + res.VUB, err = s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, vubP, prm.method, prm.args...) return res, err } diff --git a/pkg/services/control/ir/server/calls.go b/pkg/services/control/ir/server/calls.go index 537905840..4224ea81c 100644 --- a/pkg/services/control/ir/server/calls.go +++ b/pkg/services/control/ir/server/calls.go @@ -53,9 +53,11 @@ func (s *Server) TickEpoch(_ context.Context, req *control.TickEpochRequest) (*c return nil, fmt.Errorf("getting current epoch: %w", err) } - if err := s.netmapClient.NewEpoch(epoch+1, true); err != nil { + vub, err := s.netmapClient.NewEpoch(epoch+1, req.GetBody().GetVub(), true) + if err != nil { return nil, fmt.Errorf("forcing new epoch: %w", err) } + resp.Body.Vub = vub if err := SignMessage(&s.prm.key.PrivateKey, resp); err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -93,9 +95,11 @@ func (s *Server) RemoveNode(_ context.Context, req *control.RemoveNodeRequest) ( return nil, status.Error(codes.FailedPrecondition, "node is already offline") } - if err := s.netmapClient.ForceRemovePeer(nodeInfo); err != nil { + vub, err := s.netmapClient.ForceRemovePeer(nodeInfo, req.GetBody().GetVub()) + if err != nil { return nil, fmt.Errorf("forcing node removal: %w", err) } + resp.Body.Vub = vub if err := SignMessage(&s.prm.key.PrivateKey, resp); err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -113,13 +117,15 @@ func (s *Server) RemoveContainer(_ context.Context, req *control.RemoveContainer if len(req.Body.GetContainerId()) > 0 && len(req.Body.GetOwner()) > 0 { return nil, status.Error(codes.InvalidArgument, "specify the owner and container at the same time is not allowed") } - + var vub uint32 if len(req.Body.GetContainerId()) > 0 { var containerID cid.ID if err := containerID.Decode(req.Body.GetContainerId()); err != nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to parse container ID: %s", err.Error())) } - if err := s.removeContainer(containerID); err != nil { + var err error + vub, err = s.removeContainer(containerID, req.GetBody().GetVub()) + if err != nil { return nil, err } } else { @@ -138,14 +144,17 @@ func (s *Server) RemoveContainer(_ context.Context, req *control.RemoveContainer } for _, containerID := range cids { - if err := s.removeContainer(containerID); err != nil { + vub, err = s.removeContainer(containerID, req.GetBody().GetVub()) + if err != nil { return nil, err } } } resp := &control.RemoveContainerResponse{ - Body: &control.RemoveContainerResponse_Body{}, + Body: &control.RemoveContainerResponse_Body{ + Vub: vub, + }, } if err := SignMessage(&s.prm.key.PrivateKey, resp); err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -153,13 +162,15 @@ func (s *Server) RemoveContainer(_ context.Context, req *control.RemoveContainer return resp, nil } -func (s *Server) removeContainer(containerID cid.ID) error { +func (s *Server) removeContainer(containerID cid.ID, vub uint32) (uint32, error) { var prm container.DeletePrm prm.SetCID(containerID[:]) prm.SetControlTX(true) + prm.SetVUB(vub) - if err := s.containerClient.Delete(prm); err != nil { - return fmt.Errorf("forcing container removal: %w", err) + vub, err := s.containerClient.Delete(prm) + if err != nil { + return 0, fmt.Errorf("forcing container removal: %w", err) } - return nil + return vub, nil }