diff --git a/cmd/frostfs-node/cache.go b/cmd/frostfs-node/cache.go index 248c92ef..eda69149 100644 --- a/cmd/frostfs-node/cache.go +++ b/cmd/frostfs-node/cache.go @@ -242,7 +242,7 @@ func newCachedContainerLister(c *cntClient.Client, ttl time.Duration) ttlContain } } - list, err := c.List(id) + list, err := c.ContainersOf(id) if err != nil { return nil, err } @@ -260,7 +260,7 @@ func newCachedContainerLister(c *cntClient.Client, ttl time.Duration) ttlContain // the cache. func (s ttlContainerLister) List(id *user.ID) ([]cid.ID, error) { if id == nil { - return s.client.List(nil) + return s.client.ContainersOf(nil) } item, err := s.inner.get(id.EncodeToString()) diff --git a/cmd/frostfs-node/tree.go b/cmd/frostfs-node/tree.go index cee32a0b..93a36447 100644 --- a/cmd/frostfs-node/tree.go +++ b/cmd/frostfs-node/tree.go @@ -31,7 +31,7 @@ func (c cnrSource) Get(id cid.ID) (*container.Container, error) { } func (c cnrSource) List() ([]cid.ID, error) { - return c.cli.List(nil) + return c.cli.ContainersOf(nil) } func initTreeService(c *cfg) { diff --git a/pkg/innerring/processors/audit/scheduler.go b/pkg/innerring/processors/audit/scheduler.go index dd660d4e..e1a521ba 100644 --- a/pkg/innerring/processors/audit/scheduler.go +++ b/pkg/innerring/processors/audit/scheduler.go @@ -13,7 +13,7 @@ import ( var ErrInvalidIRNode = errors.New("node is not in the inner ring list") func (ap *Processor) selectContainersToAudit(epoch uint64) ([]cid.ID, error) { - containers, err := ap.containerClient.List(nil) + containers, err := ap.containerClient.ContainersOf(nil) if err != nil { return nil, fmt.Errorf("can't get list of containers to start audit: %w", err) } diff --git a/pkg/morph/client/client.go b/pkg/morph/client/client.go index 51a030e6..4e06779d 100644 --- a/pkg/morph/client/client.go +++ b/pkg/morph/client/client.go @@ -19,6 +19,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" sc "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" @@ -200,6 +201,45 @@ func (c *Client) Invoke(contract util.Uint160, fee fixedn.Fixed8, method string, return nil } +// TestInvokeIterator invokes contract method returning an iterator and executes cb on each element. +// If cb returns an error, the session is closed and this error is returned as-is. +// If the remove neo-go node does not support sessions, `unwrap.ErrNoSessionID` is returned. +func (c *Client) TestInvokeIterator(cb func(stackitem.Item) error, contract util.Uint160, method string, args ...interface{}) error { + c.switchLock.RLock() + defer c.switchLock.RUnlock() + + if c.inactive { + return ErrConnectionLost + } + + val, err := c.rpcActor.Call(contract, method, args...) + if err != nil { + return err + } else if val.State != HaltState { + return wrapFrostFSError(¬HaltStateError{state: val.State, exception: val.FaultException}) + } + + sid, r, err := unwrap.SessionIterator(val, err) + if err != nil { + return err + } + + defer func() { + _ = c.rpcActor.TerminateSession(sid) + }() + + items, err := c.rpcActor.TraverseIterator(sid, &r, 0) + for err == nil && len(items) != 0 { + for i := range items { + if err = cb(items[i]); err != nil { + return err + } + } + items, err = c.rpcActor.TraverseIterator(sid, &r, 0) + } + return err +} + // TestInvoke invokes contract method locally in neo-go node. This method should // be used to read data from smart-contract. func (c *Client) TestInvoke(contract util.Uint160, method string, args ...any) (res []stackitem.Item, err error) { diff --git a/pkg/morph/client/container/client.go b/pkg/morph/client/container/client.go index f93fd668..85d74232 100644 --- a/pkg/morph/client/container/client.go +++ b/pkg/morph/client/container/client.go @@ -22,12 +22,13 @@ type Client struct { } const ( - putMethod = "put" - deleteMethod = "delete" - getMethod = "get" - listMethod = "list" - eaclMethod = "eACL" - setEACLMethod = "setEACL" + putMethod = "put" + deleteMethod = "delete" + getMethod = "get" + listMethod = "list" + containersOfMethod = "containersOf" + eaclMethod = "eACL" + setEACLMethod = "setEACL" startEstimationMethod = "startContainerEstimation" stopEstimationMethod = "stopContainerEstimation" diff --git a/pkg/morph/client/container/containers_of.go b/pkg/morph/client/container/containers_of.go new file mode 100644 index 00000000..8a3c7220 --- /dev/null +++ b/pkg/morph/client/container/containers_of.go @@ -0,0 +1,53 @@ +package container + +import ( + "errors" + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// ContainersOf returns a list of container identifiers belonging +// to the specified user of FrostFS system. If idUser is nil, returns the list of all containers. +// +// If remote RPC does not support neo-go session API, fallback to List() method. +func (c *Client) ContainersOf(idUser *user.ID) ([]cid.ID, error) { + var rawID []byte + + if idUser != nil { + rawID = idUser.WalletBytes() + } + + var cidList []cid.ID + cb := func(item stackitem.Item) error { + rawID, err := client.BytesFromStackItem(item) + if err != nil { + return fmt.Errorf("could not get byte array from stack item (%s): %w", containersOfMethod, err) + } + + var id cid.ID + + err = id.Decode(rawID) + if err != nil { + return fmt.Errorf("decode container ID: %w", err) + } + + cidList = append(cidList, id) + return nil + } + + cnrHash := c.client.ContractAddress() + err := c.client.Morph().TestInvokeIterator(cb, cnrHash, containersOfMethod, rawID) + if err != nil { + if errors.Is(err, unwrap.ErrNoSessionID) { + return c.List(idUser) + } + return nil, err + } + + return cidList, nil +}