Validate container owner namespace when putting container #819

Merged
fyrchik merged 2 commits from dstepanov-yadro/frostfs-node:feat/ir_validate_namespace into master 2024-09-04 19:51:04 +00:00
20 changed files with 189 additions and 819 deletions

View file

@ -250,9 +250,7 @@ func (s *Server) initAlphabetProcessor(cfg *viper.Viper) error {
return err
}
func (s *Server) initContainerProcessor(cfg *viper.Viper, cnrClient *container.Client,
frostfsIDClient *frostfsid.Client,
) error {
func (s *Server) initContainerProcessor(cfg *viper.Viper, cnrClient *container.Client, frostfsIDClient *frostfsid.Client) error {
// container processor
containerProcessor, err := cont.New(&cont.Params{
Log: s.log,
@ -289,7 +287,7 @@ func (s *Server) initBalanceProcessor(cfg *viper.Viper, frostfsCli *frostfsClien
return bindMorphProcessor(balanceProcessor, s)
}
func (s *Server) initFrostFSMainnetProcessor(cfg *viper.Viper, frostfsIDClient *frostfsid.Client) error {
func (s *Server) initFrostFSMainnetProcessor(cfg *viper.Viper) error {
if s.withoutMainNet {
return nil
}
@ -299,7 +297,6 @@ func (s *Server) initFrostFSMainnetProcessor(cfg *viper.Viper, frostfsIDClient *
Metrics: s.irMetrics,
PoolSize: cfg.GetInt("workers.frostfs"),
FrostFSContract: s.contracts.frostfs,
FrostFSIDClient: frostfsIDClient,
BalanceClient: s.balanceClient,
NetmapClient: s.netmapClient,
MorphClient: s.morphClient,
@ -403,7 +400,7 @@ func (s *Server) initClientsFromMorph() (*serverMorphClients, error) {
return nil, err
}
result.FrostFSIDClient, err = frostfsid.NewFromMorph(s.morphClient, s.contracts.frostfsID, fee, frostfsid.TryNotary(), frostfsid.AsAlphabet())
result.FrostFSIDClient, err = frostfsid.NewFromMorph(s.morphClient, s.contracts.frostfsID, fee)
if err != nil {
return nil, err
}
@ -447,7 +444,7 @@ func (s *Server) initProcessors(cfg *viper.Viper, morphClients *serverMorphClien
return err
}
err = s.initFrostFSMainnetProcessor(cfg, morphClients.FrostFSIDClient)
err = s.initFrostFSMainnetProcessor(cfg)
if err != nil {
return err
}

View file

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
@ -73,20 +72,6 @@ func (cp *Processor) verifySignature(v signatureVerificationData) error {
return errors.New("invalid signature calculated by container owner's key")
}
} else {
var prm frostfsid.AccountKeysPrm
prm.SetID(v.ownerContainer)
ownerKeys, err := cp.idClient.AccountKeys(prm)
if err != nil {
return fmt.Errorf("receive owner keys %s: %w", v.ownerContainer, err)
}
for i := range ownerKeys {
if (*frostfsecdsa.PublicKeyRFC6979)(ownerKeys[i]).Verify(v.signedData, v.signature) {
return nil
}
}
}
return errors.New("signature is invalid or calculated with the key not bound to the container owner")

View file

@ -3,12 +3,13 @@ package container
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
"testing"
"time"
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
cntClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
@ -40,10 +41,10 @@ func TestPutEvent(t *testing.T) {
Log: test.NewLogger(t, true),
PoolSize: 2,
AlphabetState: &testAlphabetState{isAlphabet: true},
FrostFSIDClient: &testIDClient{},
NetworkState: nst,
ContainerClient: &testContainerClient{},
MorphClient: mc,
FrostFSIDClient: &testFrostFSIDClient{},
})
require.NoError(t, err, "failed to create processor")
@ -95,21 +96,16 @@ func TestDeleteEvent(t *testing.T) {
p, err := keys.NewPrivateKey()
require.NoError(t, err)
idc := &testIDClient{
publicKeys: []*keys.PublicKey{
p.PublicKey(),
},
}
mc := &testMorphClient{}
proc, err := New(&Params{
Log: test.NewLogger(t, true),
PoolSize: 2,
AlphabetState: &testAlphabetState{isAlphabet: true},
FrostFSIDClient: idc,
NetworkState: nst,
ContainerClient: cc,
MorphClient: mc,
FrostFSIDClient: &testFrostFSIDClient{},
})
require.NoError(t, err, "failed to create processor")
@ -139,6 +135,7 @@ func TestDeleteEvent(t *testing.T) {
ContainerIDValue: cidBin,
SignatureValue: p.Sign(cidBin),
NotaryRequestValue: nr,
PublicKeyValue: p.PublicKey().Bytes(),
}
var signature frostfscrypto.Signature
@ -177,10 +174,10 @@ func TestSetEACLEvent(t *testing.T) {
Log: test.NewLogger(t, true),
PoolSize: 2,
AlphabetState: &testAlphabetState{isAlphabet: true},
FrostFSIDClient: &testIDClient{},
NetworkState: nst,
ContainerClient: cc,
MorphClient: mc,
FrostFSIDClient: &testFrostFSIDClient{},
})
require.NoError(t, err, "failed to create processor")
@ -285,14 +282,6 @@ func (c *testContainerClient) Get(cid []byte) (*containercore.Container, error)
return nil, new(apistatus.ContainerNotFound)
}
type testIDClient struct {
publicKeys keys.PublicKeys
}
func (c *testIDClient) AccountKeys(p frostfsid.AccountKeysPrm) (keys.PublicKeys, error) {
return c.publicKeys, nil
}
var _ putEvent = &testPutEvent{}
type testPutEvent struct {
@ -332,3 +321,9 @@ func (c *testMorphClient) NotarySignAndInvokeTX(mainTx *transaction.Transaction)
c.transactions = append(c.transactions, mainTx)
return nil
}
type testFrostFSIDClient struct{}
func (c *testFrostFSIDClient) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) {
return nil, fmt.Errorf("subject not found")
}

View file

@ -2,6 +2,7 @@ package container
import (
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
@ -10,6 +11,7 @@ import (
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/util"
"go.uber.org/zap"
)
@ -88,7 +90,7 @@ func (cp *Processor) checkPutContainer(ctx *putContainerContext) error {
}
// check native name and zone
err = checkNNS(ctx, cnr)
err = cp.checkNNS(ctx, cnr)
if err != nil {
return fmt.Errorf("NNS: %w", err)
}
@ -157,7 +159,7 @@ func (cp *Processor) checkDeleteContainer(e containerEvent.Delete) error {
return nil
}
func checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error {
func (cp *Processor) checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error {
// fetch domain info
ctx.d = containerSDK.ReadDomain(cnr)
@ -175,6 +177,25 @@ func checkNNS(ctx *putContainerContext, cnr containerSDK.Container) error {
}
}
namespace, hasNamespace := strings.CutSuffix(ctx.d.Zone(), ".ns")
if !hasNamespace {
return nil
}
addr, err := util.Uint160DecodeBytesBE(cnr.Owner().WalletBytes()[1 : 1+util.Uint160Size])
if err != nil {
return fmt.Errorf("could not get container owner address: %w", err)
}
subject, err := cp.frostFSIDClient.GetSubject(addr)
if err != nil {
return fmt.Errorf("could not get subject from FrostfsID contract: %w", err)
}
if subject.Namespace != namespace {
return fmt.Errorf("container and owner namespaces do not match")
}
return nil
}

View file

@ -4,16 +4,15 @@ import (
"errors"
"fmt"
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
containerEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
"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/util"
"github.com/panjf2000/ants/v2"
"go.uber.org/zap"
@ -34,20 +33,20 @@ type (
NotarySignAndInvokeTX(mainTx *transaction.Transaction) error
}
IDClient interface {
AccountKeys(p frostfsid.AccountKeysPrm) (keys.PublicKeys, error)
FrostFSIDClient interface {
GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error)
}
// Processor of events produced by container contract in the sidechain.
Processor struct {
log *logger.Logger
metrics metrics.Register
pool *ants.Pool
alphabetState AlphabetState
cnrClient ContClient // notary must be enabled
morphClient MorphClient
idClient IDClient
netState NetworkState
log *logger.Logger
metrics metrics.Register
pool *ants.Pool
alphabetState AlphabetState
cnrClient ContClient // notary must be enabled
morphClient MorphClient
netState NetworkState
frostFSIDClient FrostFSIDClient
}
// Params of the processor constructor.
@ -58,8 +57,8 @@ type (
AlphabetState AlphabetState
ContainerClient ContClient
MorphClient MorphClient
FrostFSIDClient IDClient
NetworkState NetworkState
FrostFSIDClient FrostFSIDClient
}
)
@ -92,10 +91,10 @@ func New(p *Params) (*Processor, error) {
return nil, errors.New("ir/container: Container client is not set")
case p.MorphClient == nil:
return nil, errors.New("ir/container: Morph client is not set")
case p.FrostFSIDClient == nil:
return nil, errors.New("ir/container: FrostFS ID client is not set")
case p.NetworkState == nil:
return nil, errors.New("ir/container: network state is not set")
case p.FrostFSIDClient == nil:
return nil, errors.New("ir/container: FrostFSID client is not set")
}
p.Log.Debug(logs.ContainerContainerWorkerPool, zap.Int("size", p.PoolSize))
@ -111,14 +110,14 @@ func New(p *Params) (*Processor, error) {
}
return &Processor{
log: p.Log,
metrics: metricsRegister,
pool: pool,
alphabetState: p.AlphabetState,
cnrClient: p.ContainerClient,
idClient: p.FrostFSIDClient,
netState: p.NetworkState,
morphClient: p.MorphClient,
log: p.Log,
metrics: metricsRegister,
pool: pool,
alphabetState: p.AlphabetState,
cnrClient: p.ContainerClient,
netState: p.NetworkState,
morphClient: p.MorphClient,
frostFSIDClient: p.FrostFSIDClient,
}, nil
}

View file

@ -88,39 +88,3 @@ func (np *Processor) handleConfig(ev event.Event) {
zap.Int("capacity", np.pool.Cap()))
}
}
func (np *Processor) handleBind(ev event.Event) {
e := ev.(frostfsEvent.Bind)
np.log.Info(logs.Notification,
zap.String("type", "bind"),
)
// send event to the worker pool
err := processors.SubmitEvent(np.pool, np.metrics, "frostfs_bind", func() bool {
return np.processBind(e, true)
})
if err != nil {
// there system can be moved into controlled degradation stage
np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained,
zap.Int("capacity", np.pool.Cap()))
}
}
func (np *Processor) handleUnbind(ev event.Event) {
e := ev.(frostfsEvent.Unbind)
np.log.Info(logs.Notification,
zap.String("type", "unbind"),
)
// send event to the worker pool
err := processors.SubmitEvent(np.pool, np.metrics, "frostfs_unbind", func() bool {
return np.processBind(e, false)
})
if err != nil {
// there system can be moved into controlled degradation stage
np.log.Warn(logs.FrostFSFrostfsProcessorWorkerPoolDrained,
zap.Int("capacity", np.pool.Cap()))
}
}

View file

@ -5,12 +5,9 @@ import (
"time"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
frostfsEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
@ -194,80 +191,11 @@ func TestHandleConfig(t *testing.T) {
require.EqualValues(t, []nmClient.SetConfigPrm{expConfig}, nm.config, "invalid config value")
}
func TestHandleUnbind(t *testing.T) {
t.Parallel()
es := &testEpochState{
epochCounter: 100,
}
m := &testMorphClient{
balance: 150,
}
id := &testIDClient{}
proc, err := newTestProc(t, func(p *Params) {
p.EpochState = es
p.MorphClient = m
p.FrostFSIDClient = id
})
require.NoError(t, err, "failed to create processor")
p, err := keys.NewPrivateKey()
require.NoError(t, err)
evUnbind := frostfsEvent.Unbind{
BindCommon: frostfsEvent.BindCommon{
UserValue: util.Uint160{49}.BytesBE(),
KeysValue: [][]byte{
p.PublicKey().Bytes(),
},
TxHashValue: util.Uint256{100},
},
}
proc.handleUnbind(evUnbind)
for proc.pool.Running() > 0 {
time.Sleep(10 * time.Millisecond)
}
var userID user.ID
userID.SetScriptHash(util.Uint160{49})
var expBind frostfsid.CommonBindPrm
expBind.SetOwnerID(userID.WalletBytes())
expBind.SetKeys(evUnbind.BindCommon.KeysValue)
expBind.SetHash(evUnbind.BindCommon.TxHashValue)
var expNilSlice []frostfsid.CommonBindPrm
require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.remove, "invalid remove keys value")
require.EqualValues(t, expNilSlice, id.add, "invalid add keys value")
evBind := frostfsEvent.Bind{
BindCommon: frostfsEvent.BindCommon{
UserValue: util.Uint160{49}.BytesBE(),
KeysValue: [][]byte{
p.PublicKey().Bytes(),
},
TxHashValue: util.Uint256{100},
},
}
proc.handleBind(evBind)
for proc.pool.Running() > 0 {
time.Sleep(10 * time.Millisecond)
}
require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.remove, "invalid remove keys value")
require.EqualValues(t, []frostfsid.CommonBindPrm{expBind}, id.add, "invalid add keys value")
}
func newTestProc(t *testing.T, nonDefault func(p *Params)) (*Processor, error) {
p := &Params{
Log: test.NewLogger(t, true),
PoolSize: 1,
FrostFSContract: util.Uint160{0},
FrostFSIDClient: &testIDClient{},
BalanceClient: &testBalaceClient{},
NetmapClient: &testNetmapClient{},
MorphClient: &testMorphClient{},
@ -358,18 +286,3 @@ func (c *testMorphClient) TransferGas(receiver util.Uint160, amount fixedn.Fixed
})
return nil
}
type testIDClient struct {
add []frostfsid.CommonBindPrm
remove []frostfsid.CommonBindPrm
}
func (c *testIDClient) AddKeys(p frostfsid.CommonBindPrm) error {
c.add = append(c.add, p)
return nil
}
func (c *testIDClient) RemoveKeys(args frostfsid.CommonBindPrm) error {
c.remove = append(c.remove, args)
return nil
}

View file

@ -1,109 +0,0 @@
package frostfs
import (
"crypto/elliptic"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"go.uber.org/zap"
)
type bindCommon interface {
User() []byte
Keys() [][]byte
TxHash() util.Uint256
}
func (np *Processor) processBind(e bindCommon, bind bool) bool {
if !np.alphabetState.IsAlphabet() {
np.log.Info(logs.FrostFSNonAlphabetModeIgnoreBind)
return true
}
c := &bindCommonContext{
bindCommon: e,
bind: bind,
}
err := np.checkBindCommon(c)
if err != nil {
np.log.Error(logs.FrostFSInvalidManageKeyEvent,
zap.Bool("bind", c.bind),
zap.String("error", err.Error()),
)
return false
}
return np.approveBindCommon(c) == nil
}
type bindCommonContext struct {
bindCommon
bind bool
scriptHash util.Uint160
}
func (np *Processor) checkBindCommon(e *bindCommonContext) error {
var err error
e.scriptHash, err = util.Uint160DecodeBytesBE(e.User())
if err != nil {
return err
}
curve := elliptic.P256()
for _, key := range e.Keys() {
_, err = keys.NewPublicKeyFromBytes(key, curve)
if err != nil {
return err
}
}
return nil
}
func (np *Processor) approveBindCommon(e *bindCommonContext) error {
// calculate wallet address
scriptHash := e.User()
u160, err := util.Uint160DecodeBytesBE(scriptHash)
if err != nil {
np.log.Error(logs.FrostFSCouldNotDecodeScriptHashFromBytes,
zap.String("error", err.Error()),
)
return err
}
var id user.ID
id.SetScriptHash(u160)
prm := frostfsid.CommonBindPrm{}
prm.SetOwnerID(id.WalletBytes())
prm.SetKeys(e.Keys())
prm.SetHash(e.bindCommon.TxHash())
var typ string
if e.bind {
typ = "bind"
err = np.frostfsIDClient.AddKeys(prm)
} else {
typ = "unbind"
err = np.frostfsIDClient.RemoveKeys(prm)
}
if err != nil {
np.log.Error(fmt.Sprintf("could not approve %s", typ),
zap.String("error", err.Error()))
}
return err
}

View file

@ -8,7 +8,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/frostfsid"
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
frostfsEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/frostfs"
@ -51,11 +50,6 @@ type (
TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error
}
IDClient interface {
AddKeys(p frostfsid.CommonBindPrm) error
RemoveKeys(args frostfsid.CommonBindPrm) error
}
// Processor of events produced by frostfs contract in main net.
Processor struct {
log *logger.Logger
@ -73,7 +67,6 @@ type (
mintEmitThreshold uint64
mintEmitValue fixedn.Fixed8
gasBalanceThreshold int64
frostfsIDClient IDClient
}
// Params of the processor constructor.
@ -82,7 +75,6 @@ type (
Metrics metrics.Register
PoolSize int
FrostFSContract util.Uint160
FrostFSIDClient IDClient
BalanceClient BalanceClient
NetmapClient NetmapClient
MorphClient MorphClient
@ -101,8 +93,6 @@ const (
withdrawNotification = "Withdraw"
chequeNotification = "Cheque"
configNotification = "SetConfig"
bindNotification = "Bind"
unbindNotification = "Unbind"
)
// New creates frostfs mainnet contract processor instance.
@ -152,8 +142,6 @@ func New(p *Params) (*Processor, error) {
mintEmitThreshold: p.MintEmitThreshold,
mintEmitValue: p.MintEmitValue,
gasBalanceThreshold: p.GasBalanceThreshold,
frostfsIDClient: p.FrostFSIDClient,
}, nil
}
@ -187,16 +175,6 @@ func (np *Processor) ListenerNotificationParsers() []event.NotificationParserInf
p.SetParser(frostfsEvent.ParseConfig)
parsers = append(parsers, p)
// bind event
p.SetType(event.TypeFromString(bindNotification))
p.SetParser(frostfsEvent.ParseBind)
parsers = append(parsers, p)
// unbind event
p.SetType(event.TypeFromString(unbindNotification))
p.SetParser(frostfsEvent.ParseUnbind)
parsers = append(parsers, p)
return parsers
}
@ -230,16 +208,6 @@ func (np *Processor) ListenerNotificationHandlers() []event.NotificationHandlerI
h.SetHandler(np.handleConfig)
handlers = append(handlers, h)
// bind handler
h.SetType(event.TypeFromString(bindNotification))
h.SetHandler(np.handleBind)
handlers = append(handlers, h)
// unbind handler
h.SetType(event.TypeFromString(unbindNotification))
h.SetHandler(np.handleUnbind)
handlers = append(handlers, h)
return handlers
}

View file

@ -1,71 +0,0 @@
package frostfscontract
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
)
type commonBindArgs struct {
scriptHash []byte // script hash of account identifier
keys [][]byte // list of serialized public keys
client.InvokePrmOptional
}
// SetOptionalPrm sets optional client parameters.
func (x *commonBindArgs) SetOptionalPrm(op client.InvokePrmOptional) {
x.InvokePrmOptional = op
}
// SetScriptHash sets script hash of the FrostFS account identifier.
func (x *commonBindArgs) SetScriptHash(v []byte) {
x.scriptHash = v
}
// SetKeys sets a list of public keys in a binary format.
func (x *commonBindArgs) SetKeys(v [][]byte) {
x.keys = v
}
// BindKeysPrm groups parameters of BindKeys operation.
type BindKeysPrm struct {
commonBindArgs
}
// BindKeys binds list of public keys from FrostFS account by script hash.
func (x *Client) BindKeys(p BindKeysPrm) error {
prm := client.InvokePrm{}
prm.SetMethod(bindKeysMethod)
prm.SetArgs(p.scriptHash, p.keys)
prm.InvokePrmOptional = p.InvokePrmOptional
_, err := x.client.Invoke(prm)
if err != nil {
return fmt.Errorf("could not invoke method (%s): %w", bindKeysMethod, err)
}
return nil
}
// UnbindKeysPrm groups parameters of UnbindKeys operation.
type UnbindKeysPrm struct {
commonBindArgs
}
// UnbindKeys invokes the call of key unbinding method
// of FrostFS contract.
func (x *Client) UnbindKeys(args UnbindKeysPrm) error {
prm := client.InvokePrm{}
prm.SetMethod(unbindKeysMethod)
prm.SetArgs(args.scriptHash, args.keys)
prm.InvokePrmOptional = args.InvokePrmOptional
_, err := x.client.Invoke(prm)
if err != nil {
return fmt.Errorf("could not invoke method (%s): %w", unbindKeysMethod, err)
}
return nil
}

View file

@ -21,8 +21,6 @@ type Client struct {
}
const (
bindKeysMethod = "bind"
unbindKeysMethod = "unbind"
alphabetUpdateMethod = "alphabetUpdate"
chequeMethod = "cheque"
)

View file

@ -1,61 +0,0 @@
package frostfsid
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
)
type CommonBindPrm struct {
ownerID []byte // FrostFS account identifier
keys [][]byte // list of serialized public keys
client.InvokePrmOptional
}
func (x *CommonBindPrm) SetOptionalPrm(prm client.InvokePrmOptional) {
x.InvokePrmOptional = prm
}
// SetOwnerID sets FrostFS account identifier.
func (x *CommonBindPrm) SetOwnerID(v []byte) {
x.ownerID = v
}
// SetKeys sets a list of public keys in a binary format.
func (x *CommonBindPrm) SetKeys(v [][]byte) {
x.keys = v
}
// AddKeys adds a list of public keys to/from FrostFS account.
func (x *Client) AddKeys(p CommonBindPrm) error {
prm := client.InvokePrm{}
prm.SetMethod(addKeysMethod)
prm.SetArgs(p.ownerID, p.keys)
prm.InvokePrmOptional = p.InvokePrmOptional
_, err := x.client.Invoke(prm)
if err != nil {
return fmt.Errorf("could not invoke method (%s): %w", addKeysMethod, err)
}
return nil
}
// RemoveKeys removes a list of public keys to/from FrostFS account.
func (x *Client) RemoveKeys(args CommonBindPrm) error {
prm := client.InvokePrm{}
prm.SetMethod(removeKeysMethod)
prm.SetArgs(args.ownerID, args.keys)
prm.InvokePrmOptional = args.InvokePrmOptional
_, err := x.client.Invoke(prm)
if err != nil {
return fmt.Errorf("could not invoke method (%s): %w", removeKeysMethod, err)
}
return nil
}

View file

@ -20,53 +20,12 @@ type Client struct {
client *client.StaticClient // static FrostFS ID contract client
}
const (
keyListingMethod = "key"
addKeysMethod = "addKey"
removeKeysMethod = "removeKey"
)
// NewFromMorph wraps client to work with FrostFS ID contract.
func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8, opts ...Option) (*Client, error) {
o := defaultOpts()
for i := range opts {
opts[i](o)
}
sc, err := client.NewStatic(cli, contract, fee, ([]client.StaticClientOption)(*o)...)
func NewFromMorph(cli *client.Client, contract util.Uint160, fee fixedn.Fixed8) (*Client, error) {
sc, err := client.NewStatic(cli, contract, fee, client.TryNotary(), client.AsAlphabet())
if err != nil {
return nil, fmt.Errorf("could not create client of FrostFS ID contract: %w", err)
}
return &Client{client: sc}, nil
}
// Option allows to set an optional
// parameter of ClientWrapper.
type Option func(*opts)
type opts []client.StaticClientOption
func defaultOpts() *opts {
return new(opts)
}
// TryNotary returns option to enable
// notary invocation tries.
func TryNotary() Option {
return func(o *opts) {
*o = append(*o, client.TryNotary())
}
}
// AsAlphabet returns option to sign main TX
// of notary requests with client's private
// key.
//
// Considered to be used by IR nodes only.
func AsAlphabet() Option {
return func(o *opts) {
*o = append(*o, client.AsAlphabet())
}
}

View file

@ -1,54 +0,0 @@
package frostfsid
import (
"crypto/elliptic"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
// AccountKeysPrm groups parameters of AccountKeys operation.
type AccountKeysPrm struct {
id user.ID
}
// SetID sets owner ID.
func (a *AccountKeysPrm) SetID(id user.ID) {
a.id = id
}
// AccountKeys requests public keys of FrostFS account from FrostFS ID contract.
func (x *Client) AccountKeys(p AccountKeysPrm) (keys.PublicKeys, error) {
prm := client.TestInvokePrm{}
prm.SetMethod(keyListingMethod)
prm.SetArgs(p.id.WalletBytes())
items, err := x.client.TestInvoke(prm)
if err != nil {
return nil, fmt.Errorf("could not perform test invocation (%s): %w", keyListingMethod, err)
} else if ln := len(items); ln != 1 {
return nil, fmt.Errorf("unexpected stack item count (%s): %d", keyListingMethod, ln)
}
items, err = client.ArrayFromStackItem(items[0])
if err != nil {
return nil, fmt.Errorf("1st stack item must be an array (%s)", keyListingMethod)
}
pubs := make(keys.PublicKeys, len(items))
for i := range items {
rawPub, err := client.BytesFromStackItem(items[i])
if err != nil {
return nil, fmt.Errorf("invalid stack item, expected byte array (%s)", keyListingMethod)
}
pubs[i], err = keys.NewPublicKeyFromBytes(rawPub, elliptic.P256())
if err != nil {
return nil, fmt.Errorf("received invalid key (%s): %w", keyListingMethod, err)
}
}
return pubs, nil
}

View file

@ -0,0 +1,117 @@
package frostfsid
import (
"errors"
"fmt"
frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const methodGetSubject = "getSubject"
func (c *Client) GetSubject(addr util.Uint160) (*frostfsidclient.Subject, error) {
prm := client.TestInvokePrm{}
prm.SetMethod(methodGetSubject)
prm.SetArgs(addr)
res, err := c.client.TestInvoke(prm)
if err != nil {
return nil, fmt.Errorf("could not perform test invocation (%s): %w", methodGetSubject, err)
}
subj, err := parseSubject(res)
if err != nil {
return nil, fmt.Errorf("could not parse test invocation result (%s): %w", methodGetSubject, err)
}
return subj, nil
}
// parseSubject from https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/dd5919348da9731f24504e7bc485516c2ba5f11c/frostfsid/client/client.go#L592
func parseSubject(structArr []stackitem.Item) (*frostfsidclient.Subject, error) {
if len(structArr) < 5 {
return nil, errors.New("invalid response subject struct")
}
var (
err error
subj frostfsidclient.Subject
)
subj.PrimaryKey, err = unwrap.PublicKey(makeValidRes(structArr[0]))
if err != nil {
return nil, err
}
if !structArr[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(structArr[1]))
if err != nil {
return nil, err
}
}
if !structArr[2].Equals(stackitem.Null{}) {
subj.Namespace, err = unwrap.UTF8String(makeValidRes(structArr[2]))
if err != nil {
return nil, err
}
}
if !structArr[3].Equals(stackitem.Null{}) {
subj.Name, err = unwrap.UTF8String(makeValidRes(structArr[3]))
if err != nil {
return nil, err
}
}
subj.KV, err = parseMap(structArr[4])
if err != nil {
return nil, err
}
return &subj, nil
}
func makeValidRes(item stackitem.Item) (*result.Invoke, error) {
return &result.Invoke{
Stack: []stackitem.Item{item},
State: vmstate.Halt.String(),
}, nil
}
func parseMap(item stackitem.Item) (map[string]string, error) {
if item.Equals(stackitem.Null{}) {
return nil, nil
}
metaMap, err := unwrap.Map(makeValidRes(item))
if err != nil {
return nil, err
}
meta, ok := metaMap.Value().([]stackitem.MapElement)
if !ok {
return nil, errors.New("invalid map type")
}
res := make(map[string]string, len(meta))
for _, element := range meta {
key, err := element.Key.TryBytes()
if err != nil {
return nil, err
}
val, err := element.Value.TryBytes()
if err != nil {
return nil, err
}
res[string(key)] = string(val)
}
return res, nil
}

View file

@ -1,92 +0,0 @@
package frostfs
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
type Bind struct {
BindCommon
}
type BindCommon struct {
UserValue []byte
KeysValue [][]byte
// TxHashValue is used in notary environmental
// for calculating unique but same for
// all notification receivers values.
TxHashValue util.Uint256
}
// TxHash returns hash of the TX with new epoch
// notification.
func (b BindCommon) TxHash() util.Uint256 {
return b.TxHashValue
}
// MorphEvent implements Neo:Morph Event interface.
func (BindCommon) MorphEvent() {}
func (b BindCommon) Keys() [][]byte { return b.KeysValue }
func (b BindCommon) User() []byte { return b.UserValue }
func ParseBind(e *state.ContainedNotificationEvent) (event.Event, error) {
var (
ev Bind
err error
)
params, err := event.ParseStackArray(e)
if err != nil {
return nil, fmt.Errorf("could not parse stack items from notify event: %w", err)
}
err = parseBind(&ev.BindCommon, params)
if err != nil {
return nil, err
}
ev.TxHashValue = e.Container
return ev, nil
}
func parseBind(dst *BindCommon, params []stackitem.Item) error {
if ln := len(params); ln != 2 {
return event.WrongNumberOfParameters(2, ln)
}
var err error
// parse user
dst.UserValue, err = client.BytesFromStackItem(params[0])
if err != nil {
return fmt.Errorf("could not get bind user: %w", err)
}
// parse keys
bindKeys, err := client.ArrayFromStackItem(params[1])
if err != nil {
return fmt.Errorf("could not get bind keys: %w", err)
}
dst.KeysValue = make([][]byte, 0, len(bindKeys))
for i := range bindKeys {
rawKey, err := client.BytesFromStackItem(bindKeys[i])
if err != nil {
return fmt.Errorf("could not get bind public key: %w", err)
}
dst.KeysValue = append(dst.KeysValue, rawKey)
}
return nil
}

View file

@ -1,72 +0,0 @@
package frostfs
import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func TestParseBind(t *testing.T) {
var (
user = []byte{0x1, 0x2, 0x3}
publicKeys = [][]byte{
[]byte("key1"),
[]byte("key2"),
[]byte("key3"),
}
)
t.Run("wrong number of parameters", func(t *testing.T) {
prms := []stackitem.Item{
stackitem.NewMap(),
}
_, err := ParseBind(createNotifyEventFromItems(prms))
require.EqualError(t, err, event.WrongNumberOfParameters(2, len(prms)).Error())
})
t.Run("wrong first parameter", func(t *testing.T) {
_, err := ParseBind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewMap(),
}))
require.Error(t, err)
})
t.Run("wrong second parameter", func(t *testing.T) {
_, err := ParseBind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(user),
stackitem.NewMap(),
}))
require.Error(t, err)
})
t.Run("correct", func(t *testing.T) {
ev, err := ParseBind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(user),
stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(publicKeys[0]),
stackitem.NewByteArray(publicKeys[1]),
stackitem.NewByteArray(publicKeys[2]),
}),
}))
require.NoError(t, err)
e := ev.(Bind)
require.Equal(t, user, e.User())
require.Equal(t, publicKeys, e.Keys())
})
}
func createNotifyEventFromItems(items []stackitem.Item) *state.ContainedNotificationEvent {
return &state.ContainedNotificationEvent{
NotificationEvent: state.NotificationEvent{
Item: stackitem.NewArray(items),
},
}
}

View file

@ -5,6 +5,7 @@ import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
@ -84,3 +85,11 @@ func TestParseCheque(t *testing.T) {
}, ev)
})
}
func createNotifyEventFromItems(items []stackitem.Item) *state.ContainedNotificationEvent {
return &state.ContainedNotificationEvent{
NotificationEvent: state.NotificationEvent{
Item: stackitem.NewArray(items),
},
}
}

View file

@ -1,33 +0,0 @@
package frostfs
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
"github.com/nspcc-dev/neo-go/pkg/core/state"
)
type Unbind struct {
BindCommon
}
func ParseUnbind(e *state.ContainedNotificationEvent) (event.Event, error) {
var (
ev Unbind
err error
)
params, err := event.ParseStackArray(e)
if err != nil {
return nil, fmt.Errorf("could not parse stack items from notify event: %w", err)
}
err = parseBind(&ev.BindCommon, params)
if err != nil {
return nil, err
}
ev.TxHashValue = e.Container
return ev, nil
}

View file

@ -1,63 +0,0 @@
package frostfs
import (
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func TestParseUnbind(t *testing.T) {
var (
user = []byte{0x1, 0x2, 0x3}
publicKeys = [][]byte{
[]byte("key1"),
[]byte("key2"),
[]byte("key3"),
}
)
t.Run("wrong number of parameters", func(t *testing.T) {
prms := []stackitem.Item{
stackitem.NewMap(),
}
_, err := ParseUnbind(createNotifyEventFromItems(prms))
require.EqualError(t, err, event.WrongNumberOfParameters(2, len(prms)).Error())
})
t.Run("wrong first parameter", func(t *testing.T) {
_, err := ParseUnbind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewMap(),
}))
require.Error(t, err)
})
t.Run("wrong second parameter", func(t *testing.T) {
_, err := ParseUnbind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(user),
stackitem.NewMap(),
}))
require.Error(t, err)
})
t.Run("correct", func(t *testing.T) {
ev, err := ParseUnbind(createNotifyEventFromItems([]stackitem.Item{
stackitem.NewByteArray(user),
stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(publicKeys[0]),
stackitem.NewByteArray(publicKeys[1]),
stackitem.NewByteArray(publicKeys[2]),
}),
}))
require.NoError(t, err)
e := ev.(Unbind)
require.Equal(t, user, e.User())
require.Equal(t, publicKeys, e.Keys())
})
}