[#232] Use contract to get container info
Some checks failed
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 49s
/ Builds (pull_request) Successful in 1m12s
/ OCI image (pull_request) Successful in 1m33s
/ Lint (pull_request) Successful in 2m14s
/ Tests (pull_request) Successful in 51s
/ Integration tests (pull_request) Failing after 5m50s

Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
This commit is contained in:
Nikita Zinkevich 2025-04-25 10:03:16 +03:00
parent ee628617a3
commit c84b7aa27c
13 changed files with 306 additions and 89 deletions

View file

@ -23,6 +23,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
internalnet "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/net" internalnet "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/net"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/frostfs"
containerClient "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/frostfs/container"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/templates" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/templates"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
@ -39,6 +40,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
@ -275,6 +277,14 @@ func (a *app) initContainers(ctx context.Context) {
a.corsCnrID = *corsCnrID a.corsCnrID = *corsCnrID
} }
func (a *app) initRPCClient(ctx context.Context) *rpcclient.Client {
rpcCli, err := rpcclient.New(ctx, a.config().GetString(cfgRPCEndpoint), rpcclient.Options{})
if err != nil {
a.log.Fatal(logs.InitRPCClientFailed, zap.Error(err), logs.TagField(logs.TagApp))
}
return rpcCli
}
func (a *app) initAppSettings(lc *logLevelConfig) { func (a *app) initAppSettings(lc *logLevelConfig) {
a.settings = &appSettings{ a.settings = &appSettings{
reconnectInterval: fetchReconnectInterval(a.config()), reconnectInterval: fetchReconnectInterval(a.config()),
@ -741,7 +751,17 @@ func (a *app) stopServices() {
} }
func (a *app) configureRouter(workerPool *ants.Pool) { func (a *app) configureRouter(workerPool *ants.Pool) {
a.handle = handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool), a.log), workerPool) rpcCli := a.initRPCClient(a.ctx)
cnrClient, err := containerClient.New(containerClient.Config{
RPCAddress: a.config().GetString(cfgRPCEndpoint),
Contract: a.config().GetString(cfgContractsContainerName),
Key: a.key,
RPCClient: rpcCli,
})
if err != nil {
a.log.Fatal(logs.InitContainerContractFailed, zap.Error(err), logs.TagField(logs.TagApp))
}
a.handle = handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool), a.log), cnrClient, workerPool)
r := router.New() r := router.New()
r.RedirectTrailingSlash = true r.RedirectTrailingSlash = true

View file

@ -62,6 +62,8 @@ const (
defaultMultinetFallbackDelay = 300 * time.Millisecond defaultMultinetFallbackDelay = 300 * time.Millisecond
defaultContainerContractName = "container.frostfs"
cfgServer = "server" cfgServer = "server"
cfgTLSEnabled = "tls.enabled" cfgTLSEnabled = "tls.enabled"
cfgTLSCertFile = "tls.cert_file" cfgTLSCertFile = "tls.cert_file"
@ -196,6 +198,9 @@ const (
cmdConfig = "config" cmdConfig = "config"
cmdConfigDir = "config-dir" cmdConfigDir = "config-dir"
cmdListenAddress = "listen_address" cmdListenAddress = "listen_address"
// Contracts.
cfgContractsContainerName = "contracts.container.name"
) )
var ignore = map[string]struct{}{ var ignore = map[string]struct{}{
@ -400,6 +405,9 @@ func setDefaults(v *viper.Viper, flags *pflag.FlagSet) {
// multinet // multinet
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay) v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
// contracts
v.SetDefault(cfgContractsContainerName, defaultContainerContractName)
if resolveMethods, err := flags.GetStringSlice(cfgResolveOrder); err == nil { if resolveMethods, err := flags.GetStringSlice(cfgResolveOrder); err == nil {
v.SetDefault(cfgResolveOrder, resolveMethods) v.SetDefault(cfgResolveOrder, resolveMethods)
} }

View file

@ -179,3 +179,6 @@ HTTP_GW_FEATURES_TREE_POOL_NETMAP_SUPPORT=true
# Containers properties # Containers properties
HTTP_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj HTTP_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
# Container contract hash (LE) or name in NNS.
HTTP_GW_CONTRACTS_CONTAINER_NAME=container.frostfs

View file

@ -197,3 +197,8 @@ features:
containers: containers:
cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj
contracts:
container:
# Container contract hash (LE) or name in NNS.
name: container.frostfs

2
go.mod
View file

@ -3,6 +3,7 @@ module git.frostfs.info/TrueCloudLab/frostfs-http-gw
go 1.23 go 1.23
require ( require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121 git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250128150313-cfbca7fa1dfe git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250128150313-cfbca7fa1dfe
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250317082814-87bb55f992dc git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250317082814-87bb55f992dc
@ -33,7 +34,6 @@ require (
require ( require (
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e // indirect
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
git.frostfs.info/TrueCloudLab/hrw v1.2.1 // indirect git.frostfs.info/TrueCloudLab/hrw v1.2.1 // indirect
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect

View file

@ -1,14 +1,21 @@
package data package data
import ( import (
"time"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
) )
type BucketInfo struct { type BucketInfo struct {
Name string // container name from system attribute Name string // container name from system attribute
Zone string // container zone from system attribute Zone string // container zone from system attribute
CID cid.ID CID cid.ID
Owner user.ID
Created time.Time
LocationConstraint string
ObjectLockEnabled bool
HomomorphicHashDisabled bool HomomorphicHashDisabled bool
PlacementPolicy netmap.PlacementPolicy PlacementPolicy netmap.PlacementPolicy
} }

View file

@ -0,0 +1,84 @@
package handler
import (
"context"
"fmt"
"strconv"
"strings"
containerContract "git.frostfs.info/TrueCloudLab/frostfs-contract/container"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"go.uber.org/zap"
)
const (
attributeLocationConstraint = ".s3-location-constraint"
AttributeLockEnabled = "LockEnabled"
)
func (h *Handler) containerInfo(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) {
var (
err error
res *container.Container
log = h.reqLogger(ctx).With(zap.Stringer("cid", cnrID))
info = &data.BucketInfo{
CID: cnrID,
Name: cnrID.EncodeToString(),
}
)
res, err = h.cnrContract.GetContainerByID(cnrID)
if err != nil {
if strings.Contains(err.Error(), containerContract.NotFoundError) {
return nil, fmt.Errorf("get container: %s", err.Error())
}
return nil, fmt.Errorf("get frostfs container: %w", err)
}
cnr := *res
info.Owner = cnr.Owner()
if domain := container.ReadDomain(cnr); domain.Name() != "" {
info.Name = domain.Name()
info.Zone = domain.Zone()
}
info.Created = container.CreatedAt(cnr)
info.LocationConstraint = cnr.Attribute(attributeLocationConstraint)
info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr)
info.PlacementPolicy = cnr.PlacementPolicy()
attrLockEnabled := cnr.Attribute(AttributeLockEnabled)
if len(attrLockEnabled) > 0 {
info.ObjectLockEnabled, err = strconv.ParseBool(attrLockEnabled)
if err != nil {
log.Error(logs.CouldNotParseContainerObjectLockEnabledAttribute,
zap.String("lock_enabled", attrLockEnabled),
zap.Error(err),
logs.TagField(logs.TagDatapath),
)
}
}
ns, err := middleware.GetNamespace(ctx)
if err != nil {
return nil, fmt.Errorf("get namespace: %w", err)
}
zone := h.config.FormContainerZone(ns)
if zone != info.Zone {
return nil, fmt.Errorf("ns '%s' and zone '%s' are mismatched for container '%s'", zone, info.Zone, cnrID)
}
if err = h.cache.Put(info); err != nil {
h.reqLogger(ctx).Warn(logs.CouldntPutBucketIntoCache,
zap.String("bucket name", info.Name),
zap.Stringer("bucket cid", info.CID),
zap.Error(err),
logs.TagField(logs.TagDatapath))
}
return info, nil
}

View file

@ -233,6 +233,16 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) (Res
return &resObjectSearchMock{res: res}, nil return &resObjectSearchMock{res: res}, nil
} }
func (t *TestFrostFS) GetContainerByID(cid cid.ID) (*container.Container, error) {
for k, v := range t.containers {
if k == cid.EncodeToString() {
return v, nil
}
}
return nil, fmt.Errorf("container does not exist %s", cid)
}
func (t *TestFrostFS) InitMultiObjectReader(context.Context, PrmInitMultiObjectReader) (io.Reader, error) { func (t *TestFrostFS) InitMultiObjectReader(context.Context, PrmInitMultiObjectReader) (io.Reader, error) {
return nil, nil return nil, nil
} }

View file

@ -166,12 +166,18 @@ type ContainerResolver interface {
Resolve(ctx context.Context, zone, name string) (*cid.ID, error) Resolve(ctx context.Context, zone, name string) (*cid.ID, error)
} }
type ContainerContract interface {
// GetContainerByID reads a container from contract by ID.
GetContainerByID(cid.ID) (*container.Container, error)
}
type Handler struct { type Handler struct {
log *zap.Logger log *zap.Logger
frostfs FrostFS frostfs FrostFS
ownerID *user.ID ownerID *user.ID
config Config config Config
containerResolver ContainerResolver containerResolver ContainerResolver
cnrContract ContainerContract
tree layer.TreeService tree layer.TreeService
cache *cache.BucketCache cache *cache.BucketCache
workerPool *ants.Pool workerPool *ants.Pool
@ -189,7 +195,7 @@ type AppParams struct {
CORSCache *cache.CORSCache CORSCache *cache.CORSCache
} }
func New(params *AppParams, config Config, tree layer.TreeService, workerPool *ants.Pool) *Handler { func New(params *AppParams, config Config, tree layer.TreeService, rpcCli ContainerContract, workerPool *ants.Pool) *Handler {
return &Handler{ return &Handler{
log: params.Logger, log: params.Logger,
frostfs: params.FrostFS, frostfs: params.FrostFS,
@ -201,6 +207,7 @@ func New(params *AppParams, config Config, tree layer.TreeService, workerPool *a
workerPool: workerPool, workerPool: workerPool,
corsCnrID: params.CORSCnrID, corsCnrID: params.CORSCnrID,
corsCache: params.CORSCache, corsCache: params.CORSCache,
cnrContract: rpcCli,
} }
} }
@ -379,43 +386,7 @@ func (h *Handler) getBucketInfo(ctx context.Context, containerName string) (*dat
return nil, fmt.Errorf("resolve container: %w", err) return nil, fmt.Errorf("resolve container: %w", err)
} }
bktInfo, err := h.readContainer(ctx, *cnrID) return h.containerInfo(ctx, *cnrID)
if err != nil {
return nil, fmt.Errorf("read container: %w", err)
}
if err = h.cache.Put(bktInfo); err != nil {
h.reqLogger(ctx).Warn(logs.CouldntPutBucketIntoCache,
zap.String("bucket name", bktInfo.Name),
zap.Stringer("bucket cid", bktInfo.CID),
zap.Error(err),
logs.TagField(logs.TagDatapath))
}
return bktInfo, nil
}
func (h *Handler) readContainer(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) {
prm := PrmContainer{ContainerID: cnrID}
res, err := h.frostfs.Container(ctx, prm)
if err != nil {
return nil, fmt.Errorf("get frostfs container '%s': %w", cnrID.String(), err)
}
bktInfo := &data.BucketInfo{
CID: cnrID,
Name: cnrID.EncodeToString(),
}
if domain := container.ReadDomain(*res); domain.Name() != "" {
bktInfo.Name = domain.Name()
bktInfo.Zone = domain.Zone()
}
bktInfo.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(*res)
bktInfo.PlacementPolicy = res.PlacementPolicy()
return bktInfo, err
} }
func (h *Handler) browseIndex(ctx context.Context, req *fasthttp.RequestCtx, cidParam, oidParam string, isNativeList bool) { func (h *Handler) browseIndex(ctx context.Context, req *fasthttp.RequestCtx, cidParam, oidParam string, isNativeList bool) {

View file

@ -174,7 +174,7 @@ func prepareHandlerContextBase(logger *zap.Logger) (*handlerContext, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
handler := New(params, cfgMock, treeMock, workerPool) handler := New(params, cfgMock, treeMock, testFrostFS, workerPool)
return &handlerContext{ return &handlerContext{
key: key, key: key,

View file

@ -73,58 +73,61 @@ const (
FailedToReadIndexPageTemplate = "failed to read index page template" FailedToReadIndexPageTemplate = "failed to read index page template"
SetCustomIndexPageTemplate = "set custom index page template" SetCustomIndexPageTemplate = "set custom index page template"
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info" CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
InitRPCClientFailed = "init rpc client faileds"
InitContainerContractFailed = "init container contract failed"
) )
// Log messages with the "datapath" tag. // Log messages with the "datapath" tag.
const ( const (
CouldntParseCreationDate = "couldn't parse creation date" CouldntParseCreationDate = "couldn't parse creation date"
FailedToDetectContentTypeFromPayload = "failed to detect Content-Type from payload" FailedToDetectContentTypeFromPayload = "failed to detect Content-Type from payload"
FailedToAddObjectToArchive = "failed to add object to archive" FailedToAddObjectToArchive = "failed to add object to archive"
CloseZipWriter = "close zip writer" CloseZipWriter = "close zip writer"
IgnorePartEmptyFormName = "ignore part, empty form name" IgnorePartEmptyFormName = "ignore part, empty form name"
IgnorePartEmptyFilename = "ignore part, empty filename" IgnorePartEmptyFilename = "ignore part, empty filename"
CouldNotParseClientTime = "could not parse client time" CouldNotParseClientTime = "could not parse client time"
CouldNotPrepareExpirationHeader = "could not prepare expiration header" CouldNotPrepareExpirationHeader = "could not prepare expiration header"
CouldNotEncodeResponse = "could not encode response" CouldNotEncodeResponse = "could not encode response"
AddAttributeToResultObject = "add attribute to result object" AddAttributeToResultObject = "add attribute to result object"
Request = "request" Request = "request"
CouldNotFetchAndStoreBearerToken = "could not fetch and store bearer token" CouldNotFetchAndStoreBearerToken = "could not fetch and store bearer token"
CouldntPutBucketIntoCache = "couldn't put bucket info into cache" CouldntPutBucketIntoCache = "couldn't put bucket info into cache"
FailedToIterateOverResponse = "failed to iterate over search response" FailedToIterateOverResponse = "failed to iterate over search response"
InvalidCacheEntryType = "invalid cache entry type" InvalidCacheEntryType = "invalid cache entry type"
FailedToUnescapeQuery = "failed to unescape query" FailedToUnescapeQuery = "failed to unescape query"
CouldntCacheNetmap = "couldn't cache netmap" CouldntCacheNetmap = "couldn't cache netmap"
FailedToCloseReader = "failed to close reader" FailedToCloseReader = "failed to close reader"
FailedToFilterHeaders = "failed to filter headers" FailedToFilterHeaders = "failed to filter headers"
FailedToReadFileFromTar = "failed to read file from tar" FailedToReadFileFromTar = "failed to read file from tar"
FailedToGetAttributes = "failed to get attributes" FailedToGetAttributes = "failed to get attributes"
CloseGzipWriter = "close gzip writer" CloseGzipWriter = "close gzip writer"
CloseTarWriter = "close tar writer" CloseTarWriter = "close tar writer"
FailedToCreateGzipReader = "failed to create gzip reader" FailedToCreateGzipReader = "failed to create gzip reader"
GzipReaderSelected = "gzip reader selected" GzipReaderSelected = "gzip reader selected"
CouldNotReceiveMultipartForm = "could not receive multipart/form" CouldNotReceiveMultipartForm = "could not receive multipart/form"
ObjectsNotFound = "objects not found" ObjectsNotFound = "objects not found"
IteratingOverSelectedObjectsFailed = "iterating over selected objects failed" IteratingOverSelectedObjectsFailed = "iterating over selected objects failed"
FailedToGetBucketInfo = "could not get bucket info" FailedToGetBucketInfo = "could not get bucket info"
FailedToSubmitTaskToPool = "failed to submit task to pool" FailedToSubmitTaskToPool = "failed to submit task to pool"
ObjectWasDeleted = "object was deleted" ObjectWasDeleted = "object was deleted"
FailedToGetLatestVersionOfObject = "failed to get latest version of object" FailedToGetLatestVersionOfObject = "failed to get latest version of object"
FailedToCheckIfSettingsNodeExist = "failed to check if settings node exists" FailedToCheckIfSettingsNodeExist = "failed to check if settings node exists"
FailedToListObjects = "failed to list objects" FailedToListObjects = "failed to list objects"
FailedToParseTemplate = "failed to parse template" FailedToParseTemplate = "failed to parse template"
FailedToExecuteTemplate = "failed to execute template" FailedToExecuteTemplate = "failed to execute template"
FailedToUploadObject = "failed to upload object" FailedToUploadObject = "failed to upload object"
FailedToHeadObject = "failed to head object" FailedToHeadObject = "failed to head object"
FailedToGetObject = "failed to get object" FailedToGetObject = "failed to get object"
FailedToGetObjectPayload = "failed to get object payload" FailedToGetObjectPayload = "failed to get object payload"
FailedToFindObjectByAttribute = "failed to get find object by attribute" FailedToFindObjectByAttribute = "failed to get find object by attribute"
FailedToUnescapeOIDParam = "failed to unescape oid param" FailedToUnescapeOIDParam = "failed to unescape oid param"
InvalidOIDParam = "invalid oid param" InvalidOIDParam = "invalid oid param"
CouldNotGetCORSConfiguration = "could not get cors configuration" CouldNotGetCORSConfiguration = "could not get cors configuration"
EmptyOriginRequestHeader = "empty Origin request header" EmptyOriginRequestHeader = "empty Origin request header"
EmptyAccessControlRequestMethodHeader = "empty Access-Control-Request-Method request header" EmptyAccessControlRequestMethodHeader = "empty Access-Control-Request-Method request header"
CORSRuleWasNotMatched = "cors rule was not matched" CORSRuleWasNotMatched = "cors rule was not matched"
CouldntCacheCors = "couldn't cache cors" CouldntCacheCors = "couldn't cache cors"
CouldNotParseContainerObjectLockEnabledAttribute = "could not parse container object lock enabled attribute"
) )
// Log messages with the "external_storage" tag. // Log messages with the "external_storage" tag.

View file

@ -0,0 +1,72 @@
package container
import (
"fmt"
containerContract "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/container"
frostfsutil "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/service/frostfs/util"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
type Client struct {
contract *containerContract.Contract
}
type Config struct {
RPCAddress string
Contract string
Key *keys.PrivateKey
RPCClient *rpcclient.Client
}
func New(cfg Config) (*Client, error) {
contractHash, err := frostfsutil.ResolveContractHash(cfg.Contract, cfg.RPCAddress)
if err != nil {
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
}
key := cfg.Key
if key == nil {
if key, err = keys.NewPrivateKey(); err != nil {
return nil, fmt.Errorf("generate anon private key for container contract: %w", err)
}
}
acc := wallet.NewAccountFromPrivateKey(key)
act, err := actor.NewSimple(cfg.RPCClient, acc)
if err != nil {
return nil, fmt.Errorf("create new actor: %w", err)
}
return &Client{
contract: containerContract.New(act, contractHash),
}, nil
}
func (c *Client) GetContainerByID(cnrID cid.ID) (*container.Container, error) {
items, err := c.contract.Get(cnrID[:])
if err != nil {
return nil, fmt.Errorf("get container by cid: %w", err)
}
if len(items) != 4 {
return nil, fmt.Errorf("unexpected container stack item count: %d", len(items))
}
cnrBytes, err := items[0].TryBytes()
if err != nil {
return nil, fmt.Errorf("could not get byte array of container: %w", err)
}
var cnr container.Container
if err = cnr.Unmarshal(cnrBytes); err != nil {
return nil, fmt.Errorf("can't unmarshal container: %w", err)
}
return &cnr, nil
}

View file

@ -0,0 +1,34 @@
package util
import (
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// ResolveContractHash determine contract hash by resolving NNS name.
func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error) {
if hash, err := util.Uint160DecodeStringLE(contractHash); err == nil {
return hash, nil
}
splitName := strings.Split(contractHash, ".")
if len(splitName) != 2 {
return util.Uint160{}, fmt.Errorf("invalid contract name: '%s'", contractHash)
}
var domain container.Domain
domain.SetName(splitName[0])
domain.SetZone(splitName[1])
var nns ns.NNS
if err := nns.Dial(rpcAddress); err != nil {
return util.Uint160{}, fmt.Errorf("dial nns %s: %w", rpcAddress, err)
}
defer nns.Close()
return nns.ResolveContractHash(domain)
}