Initial commit

Initial public review release v0.10.0
This commit is contained in:
alexvanin 2020-07-10 17:17:51 +03:00 committed by Stanislav Bogatyrev
commit dadfd90dcd
276 changed files with 46331 additions and 0 deletions

392
lib/implementations/acl.go Normal file
View file

@ -0,0 +1,392 @@
package implementations
import (
"context"
sc "github.com/nspcc-dev/neo-go/pkg/smartcontract"
libacl "github.com/nspcc-dev/neofs-api-go/acl"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/acl"
"github.com/nspcc-dev/neofs-node/lib/blockchain/goclient"
"github.com/nspcc-dev/neofs-node/lib/container"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/pkg/errors"
)
// Consider moving ACLHelper implementation to the ACL library.
type (
// ACLHelper is an interface, that provides useful functions
// for ACL object pre-processor.
ACLHelper interface {
BasicACLGetter
ContainerOwnerChecker
}
// BasicACLGetter helper provides function to return basic ACL value.
BasicACLGetter interface {
GetBasicACL(context.Context, CID) (uint32, error)
}
// ContainerOwnerChecker checks owner of the container.
ContainerOwnerChecker interface {
IsContainerOwner(context.Context, CID, refs.OwnerID) (bool, error)
}
aclHelper struct {
cnr container.Storage
}
)
type binaryEACLSource struct {
binaryStore acl.BinaryExtendedACLSource
}
// StaticContractClient is a wrapper over Neo:Morph client
// that invokes single smart contract methods with fixed fee.
type StaticContractClient struct {
// neo-go client instance
client *goclient.Client
// contract script-hash
scScriptHash util.Uint160
// invocation fee
fee util.Fixed8
}
// MorphContainerContract is a wrapper over StaticContractClient
// for Container contract calls.
type MorphContainerContract struct {
// NeoFS Container smart-contract
containerContract StaticContractClient
// set EACL method name of container contract
eaclSetMethodName string
// get EACL method name of container contract
eaclGetMethodName string
// get container method name of container contract
cnrGetMethodName string
// put container method name of container contract
cnrPutMethodName string
// delete container method name of container contract
cnrDelMethodName string
// list containers method name of container contract
cnrListMethodName string
}
const (
errNewACLHelper = internal.Error("cannot create ACLHelper instance")
)
// GetBasicACL returns basic ACL of the container.
func (h aclHelper) GetBasicACL(ctx context.Context, cid CID) (uint32, error) {
gp := container.GetParams{}
gp.SetContext(ctx)
gp.SetCID(cid)
gResp, err := h.cnr.GetContainer(gp)
if err != nil {
return 0, err
}
return gResp.Container().BasicACL, nil
}
// IsContainerOwner returns true if provided id is an owner container.
func (h aclHelper) IsContainerOwner(ctx context.Context, cid CID, id refs.OwnerID) (bool, error) {
gp := container.GetParams{}
gp.SetContext(ctx)
gp.SetCID(cid)
gResp, err := h.cnr.GetContainer(gp)
if err != nil {
return false, err
}
return gResp.Container().OwnerID.Equal(id), nil
}
// NewACLHelper returns implementation of the ACLHelper interface.
func NewACLHelper(cnr container.Storage) (ACLHelper, error) {
if cnr == nil {
return nil, errNewACLHelper
}
return aclHelper{cnr}, nil
}
// ExtendedACLSourceFromBinary wraps BinaryExtendedACLSource and returns ExtendedACLSource.
//
// If passed BinaryExtendedACLSource is nil, acl.ErrNilBinaryExtendedACLStore returns.
func ExtendedACLSourceFromBinary(v acl.BinaryExtendedACLSource) (acl.ExtendedACLSource, error) {
if v == nil {
return nil, acl.ErrNilBinaryExtendedACLStore
}
return &binaryEACLSource{
binaryStore: v,
}, nil
}
// GetExtendedACLTable receives eACL table in a binary representation from storage,
// unmarshals it and returns ExtendedACLTable interface.
func (s binaryEACLSource) GetExtendedACLTable(ctx context.Context, cid refs.CID) (libacl.ExtendedACLTable, error) {
key := acl.BinaryEACLKey{}
key.SetCID(cid)
val, err := s.binaryStore.GetBinaryEACL(ctx, key)
if err != nil {
return nil, err
}
eacl := val.EACL()
// TODO: verify signature
res := libacl.WrapEACLTable(nil)
return res, res.UnmarshalBinary(eacl)
}
// NewStaticContractClient initializes a new StaticContractClient.
//
// If passed Client is nil, goclient.ErrNilClient returns.
func NewStaticContractClient(client *goclient.Client, scHash util.Uint160, fee util.Fixed8) (StaticContractClient, error) {
res := StaticContractClient{
client: client,
scScriptHash: scHash,
fee: fee,
}
var err error
if client == nil {
err = goclient.ErrNilClient
}
return res, err
}
// Invoke calls Invoke method of goclient with predefined script hash and fee.
// Supported args types are the same as in goclient.
//
// If Client is not initialized, goclient.ErrNilClient returns.
func (s StaticContractClient) Invoke(method string, args ...interface{}) error {
if s.client == nil {
return goclient.ErrNilClient
}
return s.client.Invoke(
s.scScriptHash,
s.fee,
method,
args...,
)
}
// TestInvoke calls TestInvoke method of goclient with predefined script hash.
//
// If Client is not initialized, goclient.ErrNilClient returns.
func (s StaticContractClient) TestInvoke(method string, args ...interface{}) ([]sc.Parameter, error) {
if s.client == nil {
return nil, goclient.ErrNilClient
}
return s.client.TestInvoke(
s.scScriptHash,
method,
args...,
)
}
// SetContainerContractClient is a container contract client setter.
func (s *MorphContainerContract) SetContainerContractClient(v StaticContractClient) {
s.containerContract = v
}
// SetEACLGetMethodName is a container contract Get EACL method name setter.
func (s *MorphContainerContract) SetEACLGetMethodName(v string) {
s.eaclGetMethodName = v
}
// SetEACLSetMethodName is a container contract Set EACL method name setter.
func (s *MorphContainerContract) SetEACLSetMethodName(v string) {
s.eaclSetMethodName = v
}
// SetContainerGetMethodName is a container contract Get method name setter.
func (s *MorphContainerContract) SetContainerGetMethodName(v string) {
s.cnrGetMethodName = v
}
// SetContainerPutMethodName is a container contract Put method name setter.
func (s *MorphContainerContract) SetContainerPutMethodName(v string) {
s.cnrPutMethodName = v
}
// SetContainerDeleteMethodName is a container contract Delete method name setter.
func (s *MorphContainerContract) SetContainerDeleteMethodName(v string) {
s.cnrDelMethodName = v
}
// SetContainerListMethodName is a container contract List method name setter.
func (s *MorphContainerContract) SetContainerListMethodName(v string) {
s.cnrListMethodName = v
}
// GetBinaryEACL performs the test invocation call of GetEACL method of NeoFS Container contract.
func (s *MorphContainerContract) GetBinaryEACL(_ context.Context, key acl.BinaryEACLKey) (acl.BinaryEACLValue, error) {
res := acl.BinaryEACLValue{}
prms, err := s.containerContract.TestInvoke(
s.eaclGetMethodName,
key.CID().Bytes(),
)
if err != nil {
return res, err
} else if ln := len(prms); ln != 1 {
return res, errors.Errorf("unexpected stack parameter count: %d", ln)
}
eacl, err := goclient.BytesFromStackParameter(prms[0])
if err == nil {
res.SetEACL(eacl)
}
return res, err
}
// PutBinaryEACL invokes the call of SetEACL method of NeoFS Container contract.
func (s *MorphContainerContract) PutBinaryEACL(_ context.Context, key acl.BinaryEACLKey, val acl.BinaryEACLValue) error {
return s.containerContract.Invoke(
s.eaclSetMethodName,
key.CID().Bytes(),
val.EACL(),
val.Signature(),
)
}
// GetContainer performs the test invocation call of Get method of NeoFS Container contract.
func (s *MorphContainerContract) GetContainer(p container.GetParams) (*container.GetResult, error) {
prms, err := s.containerContract.TestInvoke(
s.cnrGetMethodName,
p.CID().Bytes(),
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count: %d", ln)
}
cnrBytes, err := goclient.BytesFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get byte array from stack item")
}
cnr := new(container.Container)
if err := cnr.Unmarshal(cnrBytes); err != nil {
return nil, errors.Wrap(err, "could not unmarshal container from bytes")
}
res := new(container.GetResult)
res.SetContainer(cnr)
return res, nil
}
// PutContainer invokes the call of Put method of NeoFS Container contract.
func (s *MorphContainerContract) PutContainer(p container.PutParams) (*container.PutResult, error) {
cnr := p.Container()
cid, err := cnr.ID()
if err != nil {
return nil, errors.Wrap(err, "could not calculate container ID")
}
cnrBytes, err := cnr.Marshal()
if err != nil {
return nil, errors.Wrap(err, "could not marshal container")
}
if err := s.containerContract.Invoke(
s.cnrPutMethodName,
cnr.OwnerID.Bytes(),
cnrBytes,
[]byte{},
); err != nil {
return nil, errors.Wrap(err, "could not invoke contract method")
}
res := new(container.PutResult)
res.SetCID(cid)
return res, nil
}
// DeleteContainer invokes the call of Delete method of NeoFS Container contract.
func (s *MorphContainerContract) DeleteContainer(p container.DeleteParams) (*container.DeleteResult, error) {
if err := s.containerContract.Invoke(
s.cnrDelMethodName,
p.CID().Bytes(),
p.OwnerID().Bytes(),
[]byte{},
); err != nil {
return nil, errors.Wrap(err, "could not invoke contract method")
}
return new(container.DeleteResult), nil
}
// ListContainers performs the test invocation call of Get method of NeoFS Container contract.
//
// If owner ID list in parameters is non-empty, bytes of first owner are attached to call.
func (s *MorphContainerContract) ListContainers(p container.ListParams) (*container.ListResult, error) {
args := make([]interface{}, 0, 1)
if ownerIDList := p.OwnerIDList(); len(ownerIDList) > 0 {
args = append(args, ownerIDList[0].Bytes())
}
prms, err := s.containerContract.TestInvoke(
s.cnrListMethodName,
args...,
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count: %d", ln)
}
prms, err = goclient.ArrayFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get stack item array from stack item")
}
cidList := make([]CID, 0, len(prms))
for i := range prms {
cidBytes, err := goclient.BytesFromStackParameter(prms[i])
if err != nil {
return nil, errors.Wrap(err, "could not get byte array from stack item")
}
cid, err := refs.CIDFromBytes(cidBytes)
if err != nil {
return nil, errors.Wrap(err, "could not get container ID from bytes")
}
cidList = append(cidList, cid)
}
res := new(container.ListResult)
res.SetCIDList(cidList)
return res, nil
}

View file

@ -0,0 +1,19 @@
package implementations
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStaticContractClient(t *testing.T) {
s := new(StaticContractClient)
require.NotPanics(t, func() {
_, _ = s.TestInvoke("")
})
require.NotPanics(t, func() {
_ = s.Invoke("")
})
}

View file

@ -0,0 +1,141 @@
package implementations
import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-node/lib/blockchain/goclient"
"github.com/pkg/errors"
)
// MorphBalanceContract is a wrapper over NeoFS Balance contract client
// that provides an interface of manipulations with user funds.
type MorphBalanceContract struct {
// NeoFS Balance smart-contract
balanceContract StaticContractClient
// "balance of" method name of balance contract
balanceOfMethodName string
// decimals method name of balance contract
decimalsMethodName string
}
// BalanceOfParams is a structure that groups the parameters
// for NeoFS user balance receiving operation.
type BalanceOfParams struct {
owner refs.OwnerID
}
// BalanceOfResult is a structure that groups the values
// of the result of NeoFS user balance receiving operation.
type BalanceOfResult struct {
amount int64
}
// DecimalsParams is a structure that groups the parameters
// for NeoFS token decimals receiving operation.
type DecimalsParams struct {
}
// DecimalsResult is a structure that groups the values
// of the result of NeoFS token decimals receiving operation.
type DecimalsResult struct {
dec int64
}
// SetBalanceContractClient is a Balance contract client setter.
func (s *MorphBalanceContract) SetBalanceContractClient(v StaticContractClient) {
s.balanceContract = v
}
// SetBalanceOfMethodName is a Balance contract balanceOf method name setter.
func (s *MorphBalanceContract) SetBalanceOfMethodName(v string) {
s.balanceOfMethodName = v
}
// SetDecimalsMethodName is a Balance contract decimals method name setter.
func (s *MorphBalanceContract) SetDecimalsMethodName(v string) {
s.decimalsMethodName = v
}
// BalanceOf performs the test invocation call of balanceOf method of NeoFS Balance contract.
func (s MorphBalanceContract) BalanceOf(p BalanceOfParams) (*BalanceOfResult, error) {
owner := p.OwnerID()
u160, err := address.StringToUint160(owner.String())
if err != nil {
return nil, errors.Wrap(err, "could not convert wallet address to Uint160")
}
prms, err := s.balanceContract.TestInvoke(
s.balanceOfMethodName,
u160.BytesBE(),
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count (balanceOf): %d", ln)
}
amount, err := goclient.IntFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get integer stack item from stack item (amount)")
}
res := new(BalanceOfResult)
res.SetAmount(amount)
return res, nil
}
// Decimals performs the test invocation call of decimals method of NeoFS Balance contract.
func (s MorphBalanceContract) Decimals(DecimalsParams) (*DecimalsResult, error) {
prms, err := s.balanceContract.TestInvoke(
s.decimalsMethodName,
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count (decimals): %d", ln)
}
dec, err := goclient.IntFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get integer stack item from stack item (decimal)")
}
res := new(DecimalsResult)
res.SetDecimals(dec)
return res, nil
}
// SetOwnerID is an owner ID setter.
func (s *BalanceOfParams) SetOwnerID(v refs.OwnerID) {
s.owner = v
}
// OwnerID is an owner ID getter.
func (s BalanceOfParams) OwnerID() refs.OwnerID {
return s.owner
}
// SetAmount is an funds amount setter.
func (s *BalanceOfResult) SetAmount(v int64) {
s.amount = v
}
// Amount is an funds amount getter.
func (s BalanceOfResult) Amount() int64 {
return s.amount
}
// SetDecimals is a decimals setter.
func (s *DecimalsResult) SetDecimals(v int64) {
s.dec = v
}
// Decimals is a decimals getter.
func (s DecimalsResult) Decimals() int64 {
return s.dec
}

View file

@ -0,0 +1,35 @@
package implementations
import (
"testing"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/stretchr/testify/require"
)
func TestBalanceOfParams(t *testing.T) {
s := BalanceOfParams{}
owner := refs.OwnerID{1, 2, 3}
s.SetOwnerID(owner)
require.Equal(t, owner, s.OwnerID())
}
func TestBalanceOfResult(t *testing.T) {
s := BalanceOfResult{}
amount := int64(100)
s.SetAmount(amount)
require.Equal(t, amount, s.Amount())
}
func TestDecimalsResult(t *testing.T) {
s := DecimalsResult{}
dec := int64(100)
s.SetDecimals(dec)
require.Equal(t, dec, s.Decimals())
}

View file

@ -0,0 +1,311 @@
package implementations
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neofs-api-go/bootstrap"
"github.com/nspcc-dev/neofs-node/lib/blockchain/goclient"
"github.com/nspcc-dev/neofs-node/lib/boot"
"github.com/nspcc-dev/neofs-node/lib/ir"
"github.com/nspcc-dev/neofs-node/lib/netmap"
"github.com/pkg/errors"
)
// MorphNetmapContract is a wrapper over NeoFS Netmap contract client
// that provides an interface of network map manipulations.
type MorphNetmapContract struct {
// NeoFS Netmap smart-contract
netmapContract StaticContractClient
// add peer method name of netmap contract
addPeerMethodName string
// new epoch method name of netmap contract
newEpochMethodName string
// get netmap method name of netmap contract
getNetMapMethodName string
// update state method name of netmap contract
updStateMethodName string
// IR list method name of netmap contract
irListMethodName string
}
// UpdateEpochParams is a structure that groups the parameters
// for NeoFS epoch number updating.
type UpdateEpochParams struct {
epoch uint64
}
// UpdateStateParams is a structure that groups the parameters
// for NeoFS node state updating.
type UpdateStateParams struct {
st NodeState
key []byte
}
// NodeState is a type of node states enumeration.
type NodeState int64
const (
_ NodeState = iota
// StateOffline is an offline node state value.
StateOffline
)
const addPeerFixedArgNumber = 2
const nodeInfoFixedPrmNumber = 3
// SetNetmapContractClient is a Netmap contract client setter.
func (s *MorphNetmapContract) SetNetmapContractClient(v StaticContractClient) {
s.netmapContract = v
}
// SetAddPeerMethodName is a Netmap contract AddPeer method name setter.
func (s *MorphNetmapContract) SetAddPeerMethodName(v string) {
s.addPeerMethodName = v
}
// SetNewEpochMethodName is a Netmap contract NewEpoch method name setter.
func (s *MorphNetmapContract) SetNewEpochMethodName(v string) {
s.newEpochMethodName = v
}
// SetNetMapMethodName is a Netmap contract Netmap method name setter.
func (s *MorphNetmapContract) SetNetMapMethodName(v string) {
s.getNetMapMethodName = v
}
// SetUpdateStateMethodName is a Netmap contract UpdateState method name setter.
func (s *MorphNetmapContract) SetUpdateStateMethodName(v string) {
s.updStateMethodName = v
}
// SetIRListMethodName is a Netmap contract InnerRingList method name setter.
func (s *MorphNetmapContract) SetIRListMethodName(v string) {
s.irListMethodName = v
}
// AddPeer invokes the call of AddPeer method of NeoFS Netmap contract.
func (s *MorphNetmapContract) AddPeer(p boot.BootstrapPeerParams) error {
info := p.NodeInfo()
opts := info.GetOptions()
args := make([]interface{}, 0, addPeerFixedArgNumber+len(opts))
args = append(args,
// Address
[]byte(info.GetAddress()),
// Public key
info.GetPubKey(),
)
// Options
for i := range opts {
args = append(args, []byte(opts[i]))
}
return s.netmapContract.Invoke(
s.addPeerMethodName,
args...,
)
}
// UpdateEpoch invokes the call of NewEpoch method of NeoFS Netmap contract.
func (s *MorphNetmapContract) UpdateEpoch(p UpdateEpochParams) error {
return s.netmapContract.Invoke(
s.newEpochMethodName,
int64(p.Number()), // TODO: do not cast after uint64 type will become supported in client
)
}
// GetNetMap performs the test invocation call of Netmap method of NeoFS Netmap contract.
func (s *MorphNetmapContract) GetNetMap(p netmap.GetParams) (*netmap.GetResult, error) {
prms, err := s.netmapContract.TestInvoke(
s.getNetMapMethodName,
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count (Nodes): %d", ln)
}
prms, err = goclient.ArrayFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get stack item array from stack item (Nodes)")
}
nm := netmap.NewNetmap()
for i := range prms {
nodeInfo, err := nodeInfoFromStackItem(prms[i])
if err != nil {
return nil, errors.Wrapf(err, "could not parse stack item (Node #%d)", i)
}
if err := nm.AddNode(nodeInfo); err != nil {
return nil, errors.Wrapf(err, "could not add node #%d to network map", i)
}
}
res := new(netmap.GetResult)
res.SetNetMap(nm)
return res, nil
}
func nodeInfoFromStackItem(prm smartcontract.Parameter) (*bootstrap.NodeInfo, error) {
prms, err := goclient.ArrayFromStackParameter(prm)
if err != nil {
return nil, errors.Wrapf(err, "could not get stack item array (NodeInfo)")
} else if ln := len(prms); ln != nodeInfoFixedPrmNumber {
return nil, errors.Errorf("unexpected stack item count (NodeInfo): expected %d, has %d", 3, ln)
}
res := new(bootstrap.NodeInfo)
// Address
addrBytes, err := goclient.BytesFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get byte array from stack item (Address)")
}
res.Address = string(addrBytes)
// Public key
res.PubKey, err = goclient.BytesFromStackParameter(prms[1])
if err != nil {
return nil, errors.Wrap(err, "could not get byte array from stack item (Public key)")
}
// Options
prms, err = goclient.ArrayFromStackParameter(prms[2])
if err != nil {
return nil, errors.Wrapf(err, "could not get stack item array (Options)")
}
res.Options = make([]string, 0, len(prms))
for i := range prms {
optBytes, err := goclient.BytesFromStackParameter(prms[i])
if err != nil {
return nil, errors.Wrapf(err, "could not get byte array from stack item (Option #%d)", i)
}
res.Options = append(res.Options, string(optBytes))
}
return res, nil
}
// UpdateState invokes the call of UpdateState method of NeoFS Netmap contract.
func (s *MorphNetmapContract) UpdateState(p UpdateStateParams) error {
return s.netmapContract.Invoke(
s.updStateMethodName,
p.State().Int64(),
p.Key(),
)
}
// GetIRInfo performs the test invocation call of InnerRingList method of NeoFS Netmap contract.
func (s *MorphNetmapContract) GetIRInfo(ir.GetInfoParams) (*ir.GetInfoResult, error) {
prms, err := s.netmapContract.TestInvoke(
s.irListMethodName,
)
if err != nil {
return nil, errors.Wrap(err, "could not perform test invocation")
} else if ln := len(prms); ln != 1 {
return nil, errors.Errorf("unexpected stack item count (Nodes): %d", ln)
}
irInfo, err := irInfoFromStackItem(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get IR info from stack item")
}
res := new(ir.GetInfoResult)
res.SetInfo(*irInfo)
return res, nil
}
func irInfoFromStackItem(prm smartcontract.Parameter) (*ir.Info, error) {
prms, err := goclient.ArrayFromStackParameter(prm)
if err != nil {
return nil, errors.Wrap(err, "could not get stack item array")
}
nodes := make([]ir.Node, 0, len(prms))
for i := range prms {
node, err := irNodeFromStackItem(prms[i])
if err != nil {
return nil, errors.Wrapf(err, "could not get node info from stack item (IRNode #%d)", i)
}
nodes = append(nodes, *node)
}
info := new(ir.Info)
info.SetNodes(nodes)
return info, nil
}
func irNodeFromStackItem(prm smartcontract.Parameter) (*ir.Node, error) {
prms, err := goclient.ArrayFromStackParameter(prm)
if err != nil {
return nil, errors.Wrap(err, "could not get stack item array (IRNode)")
}
// Public key
keyBytes, err := goclient.BytesFromStackParameter(prms[0])
if err != nil {
return nil, errors.Wrap(err, "could not get byte array from stack item (Key)")
}
node := new(ir.Node)
node.SetKey(keyBytes)
return node, nil
}
// SetNumber is an epoch number setter.
func (s *UpdateEpochParams) SetNumber(v uint64) {
s.epoch = v
}
// Number is an epoch number getter.
func (s UpdateEpochParams) Number() uint64 {
return s.epoch
}
// SetState is a state setter.
func (s *UpdateStateParams) SetState(v NodeState) {
s.st = v
}
// State is a state getter.
func (s UpdateStateParams) State() NodeState {
return s.st
}
// SetKey is a public key setter.
func (s *UpdateStateParams) SetKey(v []byte) {
s.key = v
}
// Key is a public key getter.
func (s UpdateStateParams) Key() []byte {
return s.key
}
// Int64 converts NodeState to int64.
func (s NodeState) Int64() int64 {
return int64(s)
}

View file

@ -0,0 +1,30 @@
package implementations
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUpdateEpochParams(t *testing.T) {
s := UpdateEpochParams{}
e := uint64(100)
s.SetNumber(e)
require.Equal(t, e, s.Number())
}
func TestUpdateStateParams(t *testing.T) {
s := UpdateStateParams{}
st := NodeState(1)
s.SetState(st)
require.Equal(t, st, s.State())
key := []byte{1, 2, 3}
s.SetKey(key)
require.Equal(t, key, s.Key())
}

View file

@ -0,0 +1,7 @@
package implementations
// EpochReceiver is an interface of the container
// of NeoFS epoch number with read access.
type EpochReceiver interface {
Epoch() uint64
}

View file

@ -0,0 +1,78 @@
package implementations
import (
"context"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/query"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
"github.com/nspcc-dev/neofs-node/lib/replication"
"github.com/nspcc-dev/neofs-node/lib/transport"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
locator struct {
executor SelectiveContainerExecutor
log *zap.Logger
}
// LocatorParams groups the parameters of ObjectLocator constructor.
LocatorParams struct {
SelectiveContainerExecutor SelectiveContainerExecutor
Logger *zap.Logger
}
)
const locatorInstanceFailMsg = "could not create object locator"
var errEmptyObjectsContainerHandler = errors.New("empty container objects container handler")
func (s *locator) LocateObject(ctx context.Context, addr Address) (res []multiaddr.Multiaddr, err error) {
queryBytes, err := (&query.Query{
Filters: []query.Filter{
{
Type: query.Filter_Exact,
Name: transport.KeyID,
Value: addr.ObjectID.String(),
},
},
}).Marshal()
if err != nil {
return nil, errors.Wrap(err, "locate object failed on query marshal")
}
err = s.executor.Search(ctx, &SearchParams{
SelectiveParams: SelectiveParams{
CID: addr.CID,
TTL: service.NonForwardingTTL,
IDList: make([]ObjectID, 1),
},
SearchCID: addr.CID,
SearchQuery: queryBytes,
Handler: func(node multiaddr.Multiaddr, addrList []refs.Address) {
if len(addrList) > 0 {
res = append(res, node)
}
},
})
return
}
// NewObjectLocator constructs replication.ObjectLocator from SelectiveContainerExecutor.
func NewObjectLocator(p LocatorParams) (replication.ObjectLocator, error) {
switch {
case p.SelectiveContainerExecutor == nil:
return nil, errors.Wrap(errEmptyObjectsContainerHandler, locatorInstanceFailMsg)
case p.Logger == nil:
return nil, errors.Wrap(errEmptyLogger, locatorInstanceFailMsg)
}
return &locator{
executor: p.SelectiveContainerExecutor,
log: p.Logger,
}, nil
}

View file

@ -0,0 +1,38 @@
package implementations
import (
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
type testExecutor struct {
SelectiveContainerExecutor
}
func TestNewObjectLocator(t *testing.T) {
validParams := LocatorParams{
SelectiveContainerExecutor: new(testExecutor),
Logger: zap.L(),
}
t.Run("valid params", func(t *testing.T) {
s, err := NewObjectLocator(validParams)
require.NoError(t, err)
require.NotNil(t, s)
})
t.Run("empty logger", func(t *testing.T) {
p := validParams
p.Logger = nil
_, err := NewObjectLocator(p)
require.EqualError(t, err, errors.Wrap(errEmptyLogger, locatorInstanceFailMsg).Error())
})
t.Run("empty container handler", func(t *testing.T) {
p := validParams
p.SelectiveContainerExecutor = nil
_, err := NewObjectLocator(p)
require.EqualError(t, err, errors.Wrap(errEmptyObjectsContainerHandler, locatorInstanceFailMsg).Error())
})
}

View file

@ -0,0 +1,131 @@
package implementations
import (
"context"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/replication"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
// ObjectStorage is an interface of encapsulated ObjectReceptacle and ObjectSource pair.
ObjectStorage interface {
replication.ObjectReceptacle
replication.ObjectSource
}
objectStorage struct {
ls localstore.Localstore
executor SelectiveContainerExecutor
log *zap.Logger
}
// ObjectStorageParams groups the parameters of ObjectStorage constructor.
ObjectStorageParams struct {
Localstore localstore.Localstore
SelectiveContainerExecutor SelectiveContainerExecutor
Logger *zap.Logger
}
)
const objectSourceInstanceFailMsg = "could not create object source"
var errNilObject = errors.New("object is nil")
var errCouldNotGetObject = errors.New("could not get object from any node")
func (s *objectStorage) Put(ctx context.Context, params replication.ObjectStoreParams) error {
if params.Object == nil {
return errNilObject
} else if len(params.Nodes) == 0 {
if s.ls == nil {
return errEmptyLocalstore
}
return s.ls.Put(ctx, params.Object)
}
nodes := make([]multiaddr.Multiaddr, len(params.Nodes))
for i := range params.Nodes {
nodes[i] = params.Nodes[i].Node
}
return s.executor.Put(ctx, &PutParams{
SelectiveParams: SelectiveParams{
CID: params.Object.SystemHeader.CID,
Nodes: nodes,
TTL: service.NonForwardingTTL,
IDList: make([]ObjectID, 1),
},
Object: params.Object,
Handler: func(node multiaddr.Multiaddr, valid bool) {
if params.Handler == nil {
return
}
for i := range params.Nodes {
if params.Nodes[i].Node.Equal(node) {
params.Handler(params.Nodes[i], valid)
return
}
}
},
})
}
func (s *objectStorage) Get(ctx context.Context, addr Address) (res *Object, err error) {
if s.ls != nil {
if has, err := s.ls.Has(addr); err == nil && has {
if res, err = s.ls.Get(addr); err == nil {
return res, err
}
}
}
if err = s.executor.Get(ctx, &GetParams{
SelectiveParams: SelectiveParams{
CID: addr.CID,
TTL: service.NonForwardingTTL,
IDList: []ObjectID{addr.ObjectID},
Breaker: func(refs.Address) (cFlag ProgressControlFlag) {
if res != nil {
cFlag = BreakProgress
}
return
},
},
Handler: func(node multiaddr.Multiaddr, obj *object.Object) { res = obj },
}); err != nil {
return
} else if res == nil {
return nil, errCouldNotGetObject
}
return
}
// NewObjectStorage encapsulates Localstore and SelectiveContainerExecutor
// and returns ObjectStorage interface.
func NewObjectStorage(p ObjectStorageParams) (ObjectStorage, error) {
if p.Logger == nil {
return nil, errors.Wrap(errEmptyLogger, objectSourceInstanceFailMsg)
}
if p.Localstore == nil {
p.Logger.Warn("local storage not provided")
}
if p.SelectiveContainerExecutor == nil {
p.Logger.Warn("object container handler not provided")
}
return &objectStorage{
ls: p.Localstore,
executor: p.SelectiveContainerExecutor,
log: p.Logger,
}, nil
}

View file

@ -0,0 +1,74 @@
package implementations
import (
"crypto/ecdsa"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/peers"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
// AddressStoreComponent is an interface of encapsulated AddressStore and NodePublicKeyReceiver pair.
AddressStoreComponent interface {
AddressStore
NodePublicKeyReceiver
}
// AddressStore is an interface of the container of local Multiaddr.
AddressStore interface {
SelfAddr() (multiaddr.Multiaddr, error)
}
// NodePublicKeyReceiver is an interface of Multiaddr to PublicKey converter.
NodePublicKeyReceiver interface {
PublicKey(multiaddr.Multiaddr) *ecdsa.PublicKey
}
addressStore struct {
ps peers.Store
log *zap.Logger
}
)
const (
addressStoreInstanceFailMsg = "could not create address store"
errEmptyPeerStore = internal.Error("empty peer store")
errEmptyAddressStore = internal.Error("empty address store")
)
func (s addressStore) SelfAddr() (multiaddr.Multiaddr, error) { return s.ps.GetAddr(s.ps.SelfID()) }
func (s addressStore) PublicKey(mAddr multiaddr.Multiaddr) (res *ecdsa.PublicKey) {
if peerID, err := s.ps.AddressID(mAddr); err != nil {
s.log.Error("could not peer ID",
zap.Stringer("node", mAddr),
zap.Error(err),
)
} else if res, err = s.ps.GetPublicKey(peerID); err != nil {
s.log.Error("could not receive public key",
zap.Stringer("peer", peerID),
zap.Error(err),
)
}
return res
}
// NewAddressStore wraps peer store and returns AddressStoreComponent.
func NewAddressStore(ps peers.Store, log *zap.Logger) (AddressStoreComponent, error) {
if ps == nil {
return nil, errors.Wrap(errEmptyPeerStore, addressStoreInstanceFailMsg)
} else if log == nil {
return nil, errors.Wrap(errEmptyLogger, addressStoreInstanceFailMsg)
}
return &addressStore{
ps: ps,
log: log,
}, nil
}

View file

@ -0,0 +1,152 @@
package implementations
import (
"context"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/bootstrap"
"github.com/nspcc-dev/neofs-api-go/container"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/netmap"
"github.com/nspcc-dev/neofs-node/lib/placement"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
/*
File source code includes implementations of placement-related solutions.
Highly specialized interfaces give the opportunity to hide placement implementation in a black box for the reasons:
* placement is implementation-tied entity working with graphs, filters, etc.;
* NeoFS components are mostly needed in a small part of the solutions provided by placement;
* direct dependency from placement avoidance helps other components do not touch crucial changes in placement.
*/
type (
// CID is a type alias of
// CID from refs package of neofs-api-go.
CID = refs.CID
// SGID is a type alias of
// SGID from refs package of neofs-api-go.
SGID = refs.SGID
// ObjectID is a type alias of
// ObjectID from refs package of neofs-api-go.
ObjectID = refs.ObjectID
// Object is a type alias of
// Object from object package of neofs-api-go.
Object = object.Object
// Address is a type alias of
// Address from refs package of neofs-api-go.
Address = refs.Address
// Netmap is a type alias of
// NetMap from netmap package.
Netmap = netmap.NetMap
// ObjectPlacer is an interface of placement utility.
ObjectPlacer interface {
ContainerNodesLister
ContainerInvolvementChecker
GetNodes(ctx context.Context, addr Address, usePreviousNetMap bool, excl ...multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error)
Epoch() uint64
}
// ContainerNodesLister is an interface of container placement vector builder.
ContainerNodesLister interface {
ContainerNodes(ctx context.Context, cid CID) ([]multiaddr.Multiaddr, error)
ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]bootstrap.NodeInfo, error)
}
// ContainerInvolvementChecker is an interface of container affiliation checker.
ContainerInvolvementChecker interface {
IsContainerNode(ctx context.Context, addr multiaddr.Multiaddr, cid CID, previousNetMap bool) (bool, error)
}
objectPlacer struct {
pl placement.Component
}
)
const errEmptyPlacement = internal.Error("could not create storage lister: empty placement component")
// NewObjectPlacer wraps placement.Component and returns ObjectPlacer interface.
func NewObjectPlacer(pl placement.Component) (ObjectPlacer, error) {
if pl == nil {
return nil, errEmptyPlacement
}
return &objectPlacer{pl}, nil
}
func (v objectPlacer) ContainerNodes(ctx context.Context, cid CID) ([]multiaddr.Multiaddr, error) {
graph, err := v.pl.Query(ctx, placement.ContainerID(cid))
if err != nil {
return nil, errors.Wrap(err, "objectPlacer.ContainerNodes failed on graph query")
}
return graph.NodeList()
}
func (v objectPlacer) ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]bootstrap.NodeInfo, error) {
graph, err := v.pl.Query(ctx, placement.ContainerID(cid), placement.UsePreviousNetmap(prev))
if err != nil {
return nil, errors.Wrap(err, "objectPlacer.ContainerNodesInfo failed on graph query")
}
return graph.NodeInfo()
}
func (v objectPlacer) GetNodes(ctx context.Context, addr Address, usePreviousNetMap bool, excl ...multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) {
queryOptions := make([]placement.QueryOption, 1, 2)
queryOptions[0] = placement.ContainerID(addr.CID)
if usePreviousNetMap {
queryOptions = append(queryOptions, placement.UsePreviousNetmap(1))
}
graph, err := v.pl.Query(ctx, queryOptions...)
if err != nil {
if st, ok := status.FromError(errors.Cause(err)); ok && st.Code() == codes.NotFound {
return nil, container.ErrNotFound
}
return nil, errors.Wrap(err, "placer.GetNodes failed on graph query")
}
filter := func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket {
return bucket
}
if !addr.ObjectID.Empty() {
filter = func(group netmap.SFGroup, bucket *netmap.Bucket) *netmap.Bucket {
return bucket.GetSelection(group.Selectors, addr.ObjectID.Bytes())
}
}
return graph.Exclude(excl).Filter(filter).NodeList()
}
func (v objectPlacer) IsContainerNode(ctx context.Context, addr multiaddr.Multiaddr, cid CID, previousNetMap bool) (bool, error) {
nodes, err := v.GetNodes(ctx, Address{
CID: cid,
}, previousNetMap)
if err != nil {
return false, errors.Wrap(err, "placer.FromContainer failed on placer.GetNodes")
}
for i := range nodes {
if nodes[i].Equal(addr) {
return true, nil
}
}
return false, nil
}
func (v objectPlacer) Epoch() uint64 { return v.pl.NetworkState().Epoch }

View file

@ -0,0 +1,41 @@
package implementations
import (
"github.com/nspcc-dev/neofs-node/lib/peers"
)
// MorphReputationContract is a wrapper over NeoFS Reputation contract client
// that provides an interface of the storage of global trust values.
type MorphReputationContract struct {
// NeoFS Reputation smart-contract
repContract StaticContractClient
// put method name of reputation contract
putMethodName string
// list method name of reputation contract
listMethodName string
// public key storage
pkStore peers.PublicKeyStore
}
// SetReputationContractClient is a Reputation contract client setter.
func (s *MorphReputationContract) SetReputationContractClient(v StaticContractClient) {
s.repContract = v
}
// SetPublicKeyStore is a public key store setter.
func (s *MorphReputationContract) SetPublicKeyStore(v peers.PublicKeyStore) {
s.pkStore = v
}
// SetPutMethodName is a Reputation contract Put method name setter.
func (s *MorphReputationContract) SetPutMethodName(v string) {
s.putMethodName = v
}
// SetListMethodName is a Reputation contract List method name setter.
func (s *MorphReputationContract) SetListMethodName(v string) {
s.listMethodName = v
}

136
lib/implementations/sg.go Normal file
View file

@ -0,0 +1,136 @@
package implementations
import (
"context"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/hash"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
"github.com/nspcc-dev/neofs-api-go/storagegroup"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
// StorageGroupInfoReceiverParams groups the parameters of
// storage group information receiver.
StorageGroupInfoReceiverParams struct {
SelectiveContainerExecutor SelectiveContainerExecutor
Logger *zap.Logger
}
sgInfoRecv struct {
executor SelectiveContainerExecutor
log *zap.Logger
}
)
const locationFinderInstanceFailMsg = "could not create object location finder"
// ErrIncompleteSGInfo is returned by storage group information receiver
// that could not receive full information.
const ErrIncompleteSGInfo = internal.Error("could not receive full storage group info")
// PublicSessionToken is a context key for SessionToken.
// FIXME: temp solution for cycle import fix.
// Unify with same const from transformer pkg.
const PublicSessionToken = "public token"
// BearerToken is a context key for BearerToken.
const BearerToken = "bearer token"
// ExtendedHeaders is a context key for X-headers.
const ExtendedHeaders = "extended headers"
func (s *sgInfoRecv) GetSGInfo(ctx context.Context, cid CID, group []ObjectID) (*storagegroup.StorageGroup, error) {
var (
err error
res = new(storagegroup.StorageGroup)
hashList = make([]hash.Hash, 0, len(group))
)
m := make(map[string]struct{}, len(group))
for i := range group {
m[group[i].String()] = struct{}{}
}
// FIXME: hardcoded for simplicity.
// Function is called in next cases:
// - SG transformation on trusted node side (only in this case session token is needed);
// - SG info check on container nodes (token is not needed since system group has extra access);
// - data audit on inner ring nodes (same as previous).
var token service.SessionToken
if v, ok := ctx.Value(PublicSessionToken).(service.SessionToken); ok {
token = v
}
var bearer service.BearerToken
if v, ok := ctx.Value(BearerToken).(service.BearerToken); ok {
bearer = v
}
var extHdrs []service.ExtendedHeader
if v, ok := ctx.Value(ExtendedHeaders).([]service.ExtendedHeader); ok {
extHdrs = v
}
if err = s.executor.Head(ctx, &HeadParams{
GetParams: GetParams{
SelectiveParams: SelectiveParams{
CID: cid,
TTL: service.SingleForwardingTTL,
IDList: group,
Breaker: func(addr refs.Address) (cFlag ProgressControlFlag) {
if len(m) == 0 {
cFlag = BreakProgress
} else if _, ok := m[addr.ObjectID.String()]; !ok {
cFlag = NextAddress
}
return
},
Token: token,
Bearer: bearer,
ExtendedHeaders: extHdrs,
},
Handler: func(_ multiaddr.Multiaddr, obj *object.Object) {
_, hashHeader := obj.LastHeader(object.HeaderType(object.HomoHashHdr))
if hashHeader == nil {
return
}
hashList = append(hashList, hashHeader.Value.(*object.Header_HomoHash).HomoHash)
res.ValidationDataSize += obj.SystemHeader.PayloadLength
delete(m, obj.SystemHeader.ID.String())
},
},
FullHeaders: true,
}); err != nil {
return nil, err
} else if len(m) > 0 {
return nil, ErrIncompleteSGInfo
}
res.ValidationHash, err = hash.Concat(hashList)
return res, err
}
// NewStorageGroupInfoReceiver constructs storagegroup.InfoReceiver from SelectiveContainerExecutor.
func NewStorageGroupInfoReceiver(p StorageGroupInfoReceiverParams) (storagegroup.InfoReceiver, error) {
switch {
case p.Logger == nil:
return nil, errors.Wrap(errEmptyLogger, locationFinderInstanceFailMsg)
case p.SelectiveContainerExecutor == nil:
return nil, errors.Wrap(errEmptyObjectsContainerHandler, locationFinderInstanceFailMsg)
}
return &sgInfoRecv{
executor: p.SelectiveContainerExecutor,
log: p.Logger,
}, nil
}

View file

@ -0,0 +1,657 @@
package implementations
import (
"context"
"io"
"sync"
"time"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/hash"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/transport"
"github.com/pkg/errors"
"go.uber.org/zap"
)
/*
File source code includes implementation of unified objects container handler.
Implementation provides the opportunity to perform any logic over object container distributed in network.
Implementation holds placement and object transport implementations in a black box.
Any special logic could be tuned through passing handle parameters.
NOTE: Although the implementation of the other interfaces via OCH is the same, they are still separated in order to avoid mess.
*/
type (
// SelectiveContainerExecutor is an interface the tool that performs
// object operations in container with preconditions.
SelectiveContainerExecutor interface {
Put(context.Context, *PutParams) error
Get(context.Context, *GetParams) error
Head(context.Context, *HeadParams) error
Search(context.Context, *SearchParams) error
RangeHash(context.Context, *RangeHashParams) error
}
// PutParams groups the parameters
// of selective object Put.
PutParams struct {
SelectiveParams
Object *object.Object
Handler func(multiaddr.Multiaddr, bool)
CopiesNumber uint32
}
// GetParams groups the parameters
// of selective object Get.
GetParams struct {
SelectiveParams
Handler func(multiaddr.Multiaddr, *object.Object)
}
// HeadParams groups the parameters
// of selective object Head.
HeadParams struct {
GetParams
FullHeaders bool
}
// SearchParams groups the parameters
// of selective object Search.
SearchParams struct {
SelectiveParams
SearchCID refs.CID
SearchQuery []byte
Handler func(multiaddr.Multiaddr, []refs.Address)
}
// RangeHashParams groups the parameters
// of selective object GetRangeHash.
RangeHashParams struct {
SelectiveParams
Ranges []object.Range
Salt []byte
Handler func(multiaddr.Multiaddr, []hash.Hash)
}
// SelectiveParams groups the parameters of
// the execution of selective container operation.
SelectiveParams struct {
/* Should be set to true only if service under object transport implementations is served on localhost. */
ServeLocal bool
/* Raw option of the request */
Raw bool
/* TTL for object transport. All transport operations inherit same value. */
TTL uint32
/* Required ID of processing container. If empty or not set, an error is returned. */
CID
/* List of nodes selected for processing. If not specified => nodes will be selected during. */
Nodes []multiaddr.Multiaddr
/*
Next two parameters provide the opportunity to process selective objects in container.
At least on of non-empty IDList or Query is required, an error is returned otherwise.
*/
/* List of objects to process (overlaps query). */
IDList []refs.ObjectID
/* If no objects is indicated, query is used for selection. */
Query []byte
/*
If function provided, it is called after every successful operation.
True result breaks operation performing.
*/
Breaker func(refs.Address) ProgressControlFlag
/* Public session token */
Token service.SessionToken
/* Bearer token */
Bearer service.BearerToken
/* Extended headers */
ExtendedHeaders []service.ExtendedHeader
}
// ProgressControlFlag is an enumeration of progress control flags.
ProgressControlFlag int
// ObjectContainerHandlerParams grops the parameters of SelectiveContainerExecutor constructor.
ObjectContainerHandlerParams struct {
NodeLister ContainerNodesLister
Executor ContainerTraverseExecutor
*zap.Logger
}
simpleTraverser struct {
*sync.Once
list []multiaddr.Multiaddr
}
selectiveCnrExec struct {
cnl ContainerNodesLister
Executor ContainerTraverseExecutor
log *zap.Logger
}
metaInfo struct {
ttl uint32
raw bool
rt object.RequestType
token service.SessionToken
bearer service.BearerToken
extHdrs []service.ExtendedHeader
}
putInfo struct {
metaInfo
obj *object.Object
cn uint32
}
getInfo struct {
metaInfo
addr Address
raw bool
}
headInfo struct {
getInfo
fullHdr bool
}
searchInfo struct {
metaInfo
cid CID
query []byte
}
rangeHashInfo struct {
metaInfo
addr Address
ranges []object.Range
salt []byte
}
execItems struct {
params SelectiveParams
metaConstructor func(addr Address) transport.MetaInfo
handler transport.ResultHandler
}
searchTarget struct {
list []refs.Address
}
// ContainerTraverseExecutor is an interface of
// object operation executor with container traversing.
ContainerTraverseExecutor interface {
Execute(context.Context, TraverseParams)
}
// TraverseParams groups the parameters of container traversing.
TraverseParams struct {
TransportInfo transport.MetaInfo
Handler transport.ResultHandler
Traverser Traverser
WorkerPool WorkerPool
ExecutionInterceptor func(context.Context, multiaddr.Multiaddr) bool
}
// WorkerPool is an interface of go-routine pool
WorkerPool interface {
Submit(func()) error
}
// Traverser is an interface of container traverser.
Traverser interface {
Next(context.Context) []multiaddr.Multiaddr
}
cnrTraverseExec struct {
transport transport.ObjectTransport
}
singleRoutinePool struct{}
emptyReader struct{}
)
const (
_ ProgressControlFlag = iota
// NextAddress is a ProgressControlFlag of to go to the next address of the object.
NextAddress
// NextNode is a ProgressControlFlag of to go to the next node.
NextNode
// BreakProgress is a ProgressControlFlag to interrupt the execution.
BreakProgress
)
const (
instanceFailMsg = "could not create container objects collector"
errEmptyLogger = internal.Error("empty logger")
errEmptyNodeLister = internal.Error("empty container node lister")
errEmptyTraverseExecutor = internal.Error("empty container traverse executor")
errSelectiveParams = internal.Error("neither ID list nor query provided")
)
var errNilObjectTransport = errors.New("object transport is nil")
func (s *selectiveCnrExec) Put(ctx context.Context, p *PutParams) error {
meta := &putInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestPut,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
obj: p.Object,
cn: p.CopiesNumber,
}
return s.exec(ctx, &execItems{
params: p.SelectiveParams,
metaConstructor: func(Address) transport.MetaInfo { return meta },
handler: p,
})
}
func (s *selectiveCnrExec) Get(ctx context.Context, p *GetParams) error {
return s.exec(ctx, &execItems{
params: p.SelectiveParams,
metaConstructor: func(addr Address) transport.MetaInfo {
return &getInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestGet,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
addr: addr,
raw: p.Raw,
}
},
handler: p,
})
}
func (s *selectiveCnrExec) Head(ctx context.Context, p *HeadParams) error {
return s.exec(ctx, &execItems{
params: p.SelectiveParams,
metaConstructor: func(addr Address) transport.MetaInfo {
return &headInfo{
getInfo: getInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestHead,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
addr: addr,
raw: p.Raw,
},
fullHdr: p.FullHeaders,
}
},
handler: p,
})
}
func (s *selectiveCnrExec) Search(ctx context.Context, p *SearchParams) error {
return s.exec(ctx, &execItems{
params: p.SelectiveParams,
metaConstructor: func(Address) transport.MetaInfo {
return &searchInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestSearch,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
cid: p.SearchCID,
query: p.SearchQuery,
}
},
handler: p,
})
}
func (s *selectiveCnrExec) RangeHash(ctx context.Context, p *RangeHashParams) error {
return s.exec(ctx, &execItems{
params: p.SelectiveParams,
metaConstructor: func(addr Address) transport.MetaInfo {
return &rangeHashInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestRangeHash,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
addr: addr,
ranges: p.Ranges,
salt: p.Salt,
}
},
handler: p,
})
}
func (s *selectiveCnrExec) exec(ctx context.Context, p *execItems) error {
if err := p.params.validate(); err != nil {
return err
}
nodes, err := s.prepareNodes(ctx, &p.params)
if err != nil {
return err
}
loop:
for i := range nodes {
addrList := s.prepareAddrList(ctx, &p.params, nodes[i])
if len(addrList) == 0 {
continue
}
for j := range addrList {
if p.params.Breaker != nil {
switch cFlag := p.params.Breaker(addrList[j]); cFlag {
case NextAddress:
continue
case NextNode:
continue loop
case BreakProgress:
break loop
}
}
s.Executor.Execute(ctx, TraverseParams{
TransportInfo: p.metaConstructor(addrList[j]),
Handler: p.handler,
Traverser: newSimpleTraverser(nodes[i]),
})
}
}
return nil
}
func (s *SelectiveParams) validate() error {
switch {
case len(s.IDList) == 0 && len(s.Query) == 0:
return errSelectiveParams
default:
return nil
}
}
func (s *selectiveCnrExec) prepareNodes(ctx context.Context, p *SelectiveParams) ([]multiaddr.Multiaddr, error) {
if len(p.Nodes) > 0 {
return p.Nodes, nil
}
// If node serves Object transport service on localhost => pass single empty node
if p.ServeLocal {
// all transport implementations will use localhost by default
return []multiaddr.Multiaddr{nil}, nil
}
// Otherwise use container nodes
return s.cnl.ContainerNodes(ctx, p.CID)
}
func (s *selectiveCnrExec) prepareAddrList(ctx context.Context, p *SelectiveParams, node multiaddr.Multiaddr) []refs.Address {
var (
addrList []Address
l = len(p.IDList)
)
if l > 0 {
addrList = make([]Address, 0, l)
for i := range p.IDList {
addrList = append(addrList, Address{CID: p.CID, ObjectID: p.IDList[i]})
}
return addrList
}
handler := new(searchTarget)
s.Executor.Execute(ctx, TraverseParams{
TransportInfo: &searchInfo{
metaInfo: metaInfo{
ttl: p.TTL,
rt: object.RequestSearch,
raw: p.Raw,
token: p.Token,
bearer: p.Bearer,
extHdrs: p.ExtendedHeaders,
},
cid: p.CID,
query: p.Query,
},
Handler: handler,
Traverser: newSimpleTraverser(node),
})
return handler.list
}
func newSimpleTraverser(list ...multiaddr.Multiaddr) Traverser {
return &simpleTraverser{
Once: new(sync.Once),
list: list,
}
}
func (s *simpleTraverser) Next(context.Context) (res []multiaddr.Multiaddr) {
s.Do(func() {
res = s.list
})
return
}
func (s metaInfo) GetTTL() uint32 { return s.ttl }
func (s metaInfo) GetTimeout() time.Duration { return 0 }
func (s metaInfo) GetRaw() bool { return s.raw }
func (s metaInfo) Type() object.RequestType { return s.rt }
func (s metaInfo) GetSessionToken() service.SessionToken { return s.token }
func (s metaInfo) GetBearerToken() service.BearerToken { return s.bearer }
func (s metaInfo) ExtendedHeaders() []service.ExtendedHeader { return s.extHdrs }
func (s *putInfo) GetHead() *object.Object { return s.obj }
func (s *putInfo) Payload() io.Reader { return new(emptyReader) }
func (*emptyReader) Read(p []byte) (int, error) { return 0, io.EOF }
func (s *putInfo) CopiesNumber() uint32 {
return s.cn
}
func (s *getInfo) GetAddress() refs.Address { return s.addr }
func (s *getInfo) Raw() bool { return s.raw }
func (s *headInfo) GetFullHeaders() bool { return s.fullHdr }
func (s *searchInfo) GetCID() refs.CID { return s.cid }
func (s *searchInfo) GetQuery() []byte { return s.query }
func (s *rangeHashInfo) GetAddress() refs.Address { return s.addr }
func (s *rangeHashInfo) GetRanges() []object.Range { return s.ranges }
func (s *rangeHashInfo) GetSalt() []byte { return s.salt }
func (s *searchTarget) HandleResult(_ context.Context, _ multiaddr.Multiaddr, r interface{}, e error) {
if e == nil {
s.list = append(s.list, r.([]refs.Address)...)
}
}
// HandleResult calls Handler with:
// - Multiaddr with argument value;
// - error equality to nil.
func (s *PutParams) HandleResult(_ context.Context, node multiaddr.Multiaddr, _ interface{}, e error) {
s.Handler(node, e == nil)
}
// HandleResult calls Handler if error argument is nil with:
// - Multiaddr with argument value;
// - result casted to an Object pointer.
func (s *GetParams) HandleResult(_ context.Context, node multiaddr.Multiaddr, r interface{}, e error) {
if e == nil {
s.Handler(node, r.(*object.Object))
}
}
// HandleResult calls Handler if error argument is nil with:
// - Multiaddr with argument value;
// - result casted to Address slice.
func (s *SearchParams) HandleResult(_ context.Context, node multiaddr.Multiaddr, r interface{}, e error) {
if e == nil {
s.Handler(node, r.([]refs.Address))
}
}
// HandleResult calls Handler if error argument is nil with:
// - Multiaddr with argument value;
// - result casted to Hash slice.
func (s *RangeHashParams) HandleResult(_ context.Context, node multiaddr.Multiaddr, r interface{}, e error) {
if e == nil {
s.Handler(node, r.([]hash.Hash))
}
}
func (s *cnrTraverseExec) Execute(ctx context.Context, p TraverseParams) {
if p.WorkerPool == nil {
p.WorkerPool = new(singleRoutinePool)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
wg := new(sync.WaitGroup)
for {
select {
case <-ctx.Done():
return
default:
}
nodes := p.Traverser.Next(ctx)
if len(nodes) == 0 {
break
}
for i := range nodes {
node := nodes[i]
wg.Add(1)
if err := p.WorkerPool.Submit(func() {
defer wg.Done()
if p.ExecutionInterceptor != nil && p.ExecutionInterceptor(ctx, node) {
return
}
s.transport.Transport(ctx, transport.ObjectTransportParams{
TransportInfo: p.TransportInfo,
TargetNode: node,
ResultHandler: p.Handler,
})
}); err != nil {
wg.Done()
}
}
wg.Wait()
}
}
func (*singleRoutinePool) Submit(fn func()) error {
fn()
return nil
}
// NewObjectContainerHandler is a SelectiveContainerExecutor constructor.
func NewObjectContainerHandler(p ObjectContainerHandlerParams) (SelectiveContainerExecutor, error) {
switch {
case p.Executor == nil:
return nil, errors.Wrap(errEmptyTraverseExecutor, instanceFailMsg)
case p.Logger == nil:
return nil, errors.Wrap(errEmptyLogger, instanceFailMsg)
case p.NodeLister == nil:
return nil, errors.Wrap(errEmptyNodeLister, instanceFailMsg)
}
return &selectiveCnrExec{
cnl: p.NodeLister,
Executor: p.Executor,
log: p.Logger,
}, nil
}
// NewContainerTraverseExecutor is a ContainerTraverseExecutor executor.
func NewContainerTraverseExecutor(t transport.ObjectTransport) (ContainerTraverseExecutor, error) {
if t == nil {
return nil, errNilObjectTransport
}
return &cnrTraverseExec{transport: t}, nil
}

View file

@ -0,0 +1,405 @@
package implementations
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/sha256"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/hash"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/core"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/objutil"
"github.com/nspcc-dev/neofs-node/lib/rand"
"github.com/nspcc-dev/neofs-node/lib/replication"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
objectValidator struct {
as AddressStore
ls localstore.Localstore
executor SelectiveContainerExecutor
log *zap.Logger
saltSize int
maxRngSize uint64
rangeCount int
sltr Salitor
verifier objutil.Verifier
}
// Salitor is a salting data function.
Salitor func(data, salt []byte) []byte
// ObjectValidatorParams groups th
ObjectValidatorParams struct {
AddressStore AddressStore
Localstore localstore.Localstore
SelectiveContainerExecutor SelectiveContainerExecutor
Logger *zap.Logger
Salitor Salitor
SaltSize int
MaxPayloadRangeSize uint64
PayloadRangeCount int
Verifier objutil.Verifier
}
localHeadIntegrityVerifier struct {
keyVerifier core.OwnerKeyVerifier
}
payloadVerifier struct {
}
localIntegrityVerifier struct {
headVerifier objutil.Verifier
payloadVerifier objutil.Verifier
}
)
const (
objectValidatorInstanceFailMsg = "could not create object validator"
errEmptyLocalstore = internal.Error("empty local storage")
errEmptyObjectVerifier = internal.Error("empty object verifier")
defaultSaltSize = 64 // bytes
defaultPayloadRangeCount = 3
defaultMaxPayloadRangeSize = 64
)
const (
errBrokenHeaderStructure = internal.Error("broken header structure")
errMissingPayloadChecksumHeader = internal.Error("missing payload checksum header")
errWrongPayloadChecksum = internal.Error("wrong payload checksum")
)
func (s *objectValidator) Verify(ctx context.Context, params *replication.ObjectVerificationParams) bool {
selfAddr, err := s.as.SelfAddr()
if err != nil {
s.log.Debug("receive self address failure", zap.Error(err))
return false
}
if params.Node == nil || params.Node.Equal(selfAddr) {
return s.verifyLocal(ctx, params.Address)
}
return s.verifyRemote(ctx, params)
}
func (s *objectValidator) verifyLocal(ctx context.Context, addr Address) bool {
var (
err error
obj *Object
)
if obj, err = s.ls.Get(addr); err != nil {
s.log.Debug("get local meta information failure", zap.Error(err))
return false
} else if err = s.verifier.Verify(ctx, obj); err != nil {
s.log.Debug("integrity check failure", zap.Error(err))
}
return err == nil
}
func (s *objectValidator) verifyRemote(ctx context.Context, params *replication.ObjectVerificationParams) bool {
var (
receivedObj *Object
valid bool
)
defer func() {
if params.Handler != nil && receivedObj != nil {
params.Handler(valid, receivedObj)
}
}()
p := &HeadParams{
GetParams: GetParams{
SelectiveParams: SelectiveParams{
CID: params.CID,
Nodes: []multiaddr.Multiaddr{params.Node},
TTL: service.NonForwardingTTL,
IDList: []ObjectID{params.ObjectID},
Raw: true,
},
Handler: func(_ multiaddr.Multiaddr, obj *object.Object) {
receivedObj = obj
valid = s.verifier.Verify(ctx, obj) == nil
},
},
FullHeaders: true,
}
if err := s.executor.Head(ctx, p); err != nil || !valid {
return false
} else if receivedObj.SystemHeader.PayloadLength <= 0 || receivedObj.IsLinking() {
return true
}
if !params.LocalInvalid {
has, err := s.ls.Has(params.Address)
if err == nil && has {
obj, err := s.ls.Get(params.Address)
if err == nil {
return s.verifyThroughHashes(ctx, obj, params.Node)
}
}
}
valid = false
_ = s.executor.Get(ctx, &p.GetParams)
return valid
}
func (s *objectValidator) verifyThroughHashes(ctx context.Context, obj *Object, node multiaddr.Multiaddr) (valid bool) {
var (
salt = generateSalt(s.saltSize)
rngs = generateRanges(obj.SystemHeader.PayloadLength, s.maxRngSize, s.rangeCount)
)
_ = s.executor.RangeHash(ctx, &RangeHashParams{
SelectiveParams: SelectiveParams{
CID: obj.SystemHeader.CID,
Nodes: []multiaddr.Multiaddr{node},
TTL: service.NonForwardingTTL,
IDList: []ObjectID{obj.SystemHeader.ID},
},
Ranges: rngs,
Salt: salt,
Handler: func(node multiaddr.Multiaddr, hashes []hash.Hash) {
valid = compareHashes(s.sltr, obj.Payload, salt, rngs, hashes)
},
})
return
}
func compareHashes(sltr Salitor, payload, salt []byte, rngs []object.Range, hashes []hash.Hash) bool {
if len(rngs) != len(hashes) {
return false
}
for i := range rngs {
saltPayloadPart := sltr(payload[rngs[i].Offset:rngs[i].Offset+rngs[i].Length], salt)
if !hashes[i].Equal(hash.Sum(saltPayloadPart)) {
return false
}
}
return true
}
func generateRanges(payloadSize, maxRangeSize uint64, count int) []object.Range {
res := make([]object.Range, count)
l := min(payloadSize, maxRangeSize)
for i := 0; i < count; i++ {
res[i].Length = l
res[i].Offset = rand.Uint64(rand.New(), int64(payloadSize-l))
}
return res
}
func min(a, b uint64) uint64 {
if a < b {
return a
}
return b
}
func generateSalt(saltSize int) []byte {
salt := make([]byte, saltSize)
if _, err := rand.Read(salt); err != nil {
return nil
}
return salt
}
// NewObjectValidator constructs universal replication.ObjectVerifier.
func NewObjectValidator(p *ObjectValidatorParams) (replication.ObjectVerifier, error) {
switch {
case p.Logger == nil:
return nil, errors.Wrap(errEmptyLogger, objectValidatorInstanceFailMsg)
case p.AddressStore == nil:
return nil, errors.Wrap(errEmptyAddressStore, objectValidatorInstanceFailMsg)
case p.Localstore == nil:
return nil, errors.Wrap(errEmptyLocalstore, objectValidatorInstanceFailMsg)
case p.Verifier == nil:
return nil, errors.Wrap(errEmptyObjectVerifier, objectValidatorInstanceFailMsg)
}
if p.SaltSize <= 0 {
p.SaltSize = defaultSaltSize
}
if p.PayloadRangeCount <= 0 {
p.PayloadRangeCount = defaultPayloadRangeCount
}
if p.MaxPayloadRangeSize <= 0 {
p.MaxPayloadRangeSize = defaultMaxPayloadRangeSize
}
return &objectValidator{
as: p.AddressStore,
ls: p.Localstore,
executor: p.SelectiveContainerExecutor,
log: p.Logger,
saltSize: p.SaltSize,
maxRngSize: p.MaxPayloadRangeSize,
rangeCount: p.PayloadRangeCount,
sltr: p.Salitor,
verifier: p.Verifier,
}, nil
}
// NewLocalHeadIntegrityVerifier constructs local object head verifier and returns objutil.Verifier interface.
func NewLocalHeadIntegrityVerifier(keyVerifier core.OwnerKeyVerifier) (objutil.Verifier, error) {
if keyVerifier == nil {
return nil, core.ErrNilOwnerKeyVerifier
}
return &localHeadIntegrityVerifier{
keyVerifier: keyVerifier,
}, nil
}
// NewLocalIntegrityVerifier constructs local object verifier and returns objutil.Verifier interface.
func NewLocalIntegrityVerifier(keyVerifier core.OwnerKeyVerifier) (objutil.Verifier, error) {
if keyVerifier == nil {
return nil, core.ErrNilOwnerKeyVerifier
}
return &localIntegrityVerifier{
headVerifier: &localHeadIntegrityVerifier{
keyVerifier: keyVerifier,
},
payloadVerifier: new(payloadVerifier),
}, nil
}
// NewPayloadVerifier constructs object payload verifier and returns objutil.Verifier.
func NewPayloadVerifier() objutil.Verifier {
return new(payloadVerifier)
}
type hdrOwnerKeyContainer struct {
owner refs.OwnerID
key []byte
}
func (s hdrOwnerKeyContainer) GetOwnerID() refs.OwnerID {
return s.owner
}
func (s hdrOwnerKeyContainer) GetOwnerKey() []byte {
return s.key
}
func (s *localHeadIntegrityVerifier) Verify(ctx context.Context, obj *Object) error {
var (
checkKey *ecdsa.PublicKey
ownerKeyCnr core.OwnerKeyContainer
)
if _, h := obj.LastHeader(object.HeaderType(object.TokenHdr)); h != nil {
token := h.GetValue().(*object.Header_Token).Token
if err := service.VerifySignatureWithKey(
crypto.UnmarshalPublicKey(token.GetOwnerKey()),
service.NewVerifiedSessionToken(token),
); err != nil {
return err
}
ownerKeyCnr = token
checkKey = crypto.UnmarshalPublicKey(token.GetSessionKey())
} else if _, h := obj.LastHeader(object.HeaderType(object.PublicKeyHdr)); h != nil {
pkHdr := h.GetValue().(*object.Header_PublicKey)
if pkHdr != nil && pkHdr.PublicKey != nil {
val := pkHdr.PublicKey.GetValue()
ownerKeyCnr = &hdrOwnerKeyContainer{
owner: obj.GetSystemHeader().OwnerID,
key: val,
}
checkKey = crypto.UnmarshalPublicKey(val)
}
}
if ownerKeyCnr == nil {
return core.ErrNilOwnerKeyContainer
} else if err := s.keyVerifier.VerifyKey(ctx, ownerKeyCnr); err != nil {
return err
}
return verifyObjectIntegrity(obj, checkKey)
}
// verifyObjectIntegrity verifies integrity of object header.
// Returns error if object
// - does not contains integrity header;
// - integrity header is not a last header in object;
// - integrity header signature is broken.
func verifyObjectIntegrity(obj *Object, key *ecdsa.PublicKey) error {
n, h := obj.LastHeader(object.HeaderType(object.IntegrityHdr))
if l := len(obj.Headers); l <= 0 || n != l-1 {
return errBrokenHeaderStructure
}
integrityHdr := h.Value.(*object.Header_Integrity).Integrity
if integrityHdr == nil {
return errBrokenHeaderStructure
}
data, err := objutil.MarshalHeaders(obj, n)
if err != nil {
return err
}
hdrChecksum := sha256.Sum256(data)
return crypto.Verify(key, hdrChecksum[:], integrityHdr.ChecksumSignature)
}
func (s *payloadVerifier) Verify(_ context.Context, obj *Object) error {
if _, h := obj.LastHeader(object.HeaderType(object.PayloadChecksumHdr)); h == nil {
return errMissingPayloadChecksumHeader
} else if checksum := sha256.Sum256(obj.Payload); !bytes.Equal(
checksum[:],
h.Value.(*object.Header_PayloadChecksum).PayloadChecksum,
) {
return errWrongPayloadChecksum
}
return nil
}
func (s *localIntegrityVerifier) Verify(ctx context.Context, obj *Object) error {
if err := s.headVerifier.Verify(ctx, obj); err != nil {
return err
}
return s.payloadVerifier.Verify(ctx, obj)
}

View file

@ -0,0 +1,273 @@
package implementations
import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"math/rand"
"testing"
"github.com/multiformats/go-multiaddr"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/refs"
"github.com/nspcc-dev/neofs-api-go/service"
crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/core"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/objutil"
"github.com/nspcc-dev/neofs-node/lib/test"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
type testEntity struct {
err error
}
func (s *testEntity) Verify(context.Context, *object.Object) error { return s.err }
func (s *testEntity) SelfAddr() (multiaddr.Multiaddr, error) { panic("implement me") }
func (s *testEntity) Put(context.Context, *localstore.Object) error { panic("implement me") }
func (s *testEntity) Get(localstore.Address) (*localstore.Object, error) { panic("implement me") }
func (s *testEntity) Del(localstore.Address) error { panic("implement me") }
func (s *testEntity) Meta(localstore.Address) (*localstore.ObjectMeta, error) { panic("implement me") }
func (s *testEntity) Has(localstore.Address) (bool, error) { panic("implement me") }
func (s *testEntity) ObjectsCount() (uint64, error) { panic("implement me") }
func (s *testEntity) Size() int64 { panic("implement me") }
func (s *testEntity) Iterate(localstore.FilterPipeline, localstore.MetaHandler) error {
panic("implement me")
}
func (s *testEntity) PRead(ctx context.Context, addr refs.Address, rng object.Range) ([]byte, error) {
panic("implement me")
}
func (s *testEntity) VerifyKey(context.Context, core.OwnerKeyContainer) error {
return s.err
}
func TestNewObjectValidator(t *testing.T) {
validParams := ObjectValidatorParams{
Logger: zap.L(),
AddressStore: new(testEntity),
Localstore: new(testEntity),
Verifier: new(testEntity),
}
t.Run("valid params", func(t *testing.T) {
s, err := NewObjectValidator(&validParams)
require.NoError(t, err)
require.NotNil(t, s)
})
t.Run("fail on empty local storage", func(t *testing.T) {
p := validParams
p.Localstore = nil
_, err := NewObjectValidator(&p)
require.EqualError(t, err, errors.Wrap(errEmptyLocalstore, objectValidatorInstanceFailMsg).Error())
})
t.Run("fail on empty logger", func(t *testing.T) {
p := validParams
p.Logger = nil
_, err := NewObjectValidator(&p)
require.EqualError(t, err, errors.Wrap(errEmptyLogger, objectValidatorInstanceFailMsg).Error())
})
}
func TestNewLocalIntegrityVerifier(t *testing.T) {
var (
err error
verifier objutil.Verifier
keyVerifier = new(testEntity)
)
_, err = NewLocalHeadIntegrityVerifier(nil)
require.EqualError(t, err, core.ErrNilOwnerKeyVerifier.Error())
_, err = NewLocalIntegrityVerifier(nil)
require.EqualError(t, err, core.ErrNilOwnerKeyVerifier.Error())
verifier, err = NewLocalHeadIntegrityVerifier(keyVerifier)
require.NoError(t, err)
require.NotNil(t, verifier)
verifier, err = NewLocalIntegrityVerifier(keyVerifier)
require.NoError(t, err)
require.NotNil(t, verifier)
}
func TestLocalHeadIntegrityVerifier_Verify(t *testing.T) {
var (
ctx = context.TODO()
ownerPrivateKey = test.DecodeKey(0)
ownerPublicKey = &ownerPrivateKey.PublicKey
sessionPrivateKey = test.DecodeKey(1)
sessionPublicKey = &sessionPrivateKey.PublicKey
)
ownerID, err := refs.NewOwnerID(ownerPublicKey)
require.NoError(t, err)
s, err := NewLocalIntegrityVerifier(core.NewNeoKeyVerifier())
require.NoError(t, err)
okItems := []func() *Object{
// correct object w/ session token
func() *Object {
token := new(service.Token)
token.SetOwnerID(ownerID)
token.SetSessionKey(crypto.MarshalPublicKey(sessionPublicKey))
require.NoError(t,
service.AddSignatureWithKey(
ownerPrivateKey,
service.NewSignedSessionToken(token),
),
)
obj := new(Object)
obj.AddHeader(&object.Header{
Value: &object.Header_Token{
Token: token,
},
})
obj.SetPayload([]byte{1, 2, 3})
addPayloadChecksum(obj)
addHeadersChecksum(t, obj, sessionPrivateKey)
return obj
},
// correct object w/o session token
func() *Object {
obj := new(Object)
obj.SystemHeader.OwnerID = ownerID
obj.SetPayload([]byte{1, 2, 3})
addPayloadChecksum(obj)
obj.AddHeader(&object.Header{
Value: &object.Header_PublicKey{
PublicKey: &object.PublicKey{
Value: crypto.MarshalPublicKey(ownerPublicKey),
},
},
})
addHeadersChecksum(t, obj, ownerPrivateKey)
return obj
},
}
failItems := []func() *Object{}
for _, item := range okItems {
require.NoError(t, s.Verify(ctx, item()))
}
for _, item := range failItems {
require.Error(t, s.Verify(ctx, item()))
}
}
func addPayloadChecksum(obj *Object) {
payloadChecksum := sha256.Sum256(obj.GetPayload())
obj.AddHeader(&object.Header{
Value: &object.Header_PayloadChecksum{
PayloadChecksum: payloadChecksum[:],
},
})
}
func addHeadersChecksum(t *testing.T, obj *Object, key *ecdsa.PrivateKey) {
headersData, err := objutil.MarshalHeaders(obj, len(obj.Headers))
require.NoError(t, err)
headersChecksum := sha256.Sum256(headersData)
integrityHdr := new(object.IntegrityHeader)
integrityHdr.SetHeadersChecksum(headersChecksum[:])
require.NoError(t, service.AddSignatureWithKey(key, integrityHdr))
obj.AddHeader(&object.Header{
Value: &object.Header_Integrity{
Integrity: integrityHdr,
},
})
}
func TestPayloadVerifier_Verify(t *testing.T) {
ctx := context.TODO()
verifier := new(payloadVerifier)
t.Run("missing header", func(t *testing.T) {
obj := new(Object)
require.EqualError(t, verifier.Verify(ctx, obj), errMissingPayloadChecksumHeader.Error())
})
t.Run("correct result", func(t *testing.T) {
payload := testData(t, 10)
cs := sha256.Sum256(payload)
hdr := &object.Header_PayloadChecksum{PayloadChecksum: cs[:]}
obj := &Object{
Headers: []object.Header{{Value: hdr}},
Payload: payload,
}
require.NoError(t, verifier.Verify(ctx, obj))
hdr.PayloadChecksum[0]++
require.EqualError(t, verifier.Verify(ctx, obj), errWrongPayloadChecksum.Error())
hdr.PayloadChecksum[0]--
obj.Payload[0]++
require.EqualError(t, verifier.Verify(ctx, obj), errWrongPayloadChecksum.Error())
})
}
func TestLocalIntegrityVerifier_Verify(t *testing.T) {
ctx := context.TODO()
obj := new(Object)
t.Run("head verification failure", func(t *testing.T) {
hErr := internal.Error("test error for head verifier")
s := &localIntegrityVerifier{
headVerifier: &testEntity{
err: hErr, // force head verifier to return hErr
},
}
require.EqualError(t, s.Verify(ctx, obj), hErr.Error())
})
t.Run("correct result", func(t *testing.T) {
pErr := internal.Error("test error for payload verifier")
s := &localIntegrityVerifier{
headVerifier: new(testEntity),
payloadVerifier: &testEntity{
err: pErr, // force payload verifier to return hErr
},
}
require.EqualError(t, s.Verify(ctx, obj), pErr.Error())
})
}
// testData returns size bytes of random data.
func testData(t *testing.T, size int) []byte {
res := make([]byte, size)
_, err := rand.Read(res)
require.NoError(t, err)
return res
}
// TODO: write functionality tests