forked from TrueCloudLab/frostfs-sdk-go
[#286] reputation: Refactor and document package functionality
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
30d27c3050
commit
041e1ef2b6
7 changed files with 655 additions and 596 deletions
|
@ -65,10 +65,13 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru
|
||||||
reqBody := new(v2reputation.AnnounceLocalTrustRequestBody)
|
reqBody := new(v2reputation.AnnounceLocalTrustRequestBody)
|
||||||
reqBody.SetEpoch(prm.epoch)
|
reqBody.SetEpoch(prm.epoch)
|
||||||
|
|
||||||
trusts := make([]reputation.Trust, len(prm.trusts))
|
trusts := make([]v2reputation.Trust, len(prm.trusts))
|
||||||
copy(trusts, prm.trusts)
|
|
||||||
|
|
||||||
reqBody.SetTrusts(reputation.TrustsToV2(trusts))
|
for i := range prm.trusts {
|
||||||
|
prm.trusts[i].WriteToV2(&trusts[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody.SetTrusts(trusts)
|
||||||
|
|
||||||
// form request
|
// form request
|
||||||
var req v2reputation.AnnounceLocalTrustRequest
|
var req v2reputation.AnnounceLocalTrustRequest
|
||||||
|
@ -159,11 +162,14 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI
|
||||||
panic("current trust value not set")
|
panic("current trust value not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var trust v2reputation.PeerToPeerTrust
|
||||||
|
prm.trust.WriteToV2(&trust)
|
||||||
|
|
||||||
// form request body
|
// form request body
|
||||||
reqBody := new(v2reputation.AnnounceIntermediateResultRequestBody)
|
reqBody := new(v2reputation.AnnounceIntermediateResultRequestBody)
|
||||||
reqBody.SetEpoch(prm.epoch)
|
reqBody.SetEpoch(prm.epoch)
|
||||||
reqBody.SetIteration(prm.iter)
|
reqBody.SetIteration(prm.iter)
|
||||||
reqBody.SetTrust(prm.trust.ToV2())
|
reqBody.SetTrust(&trust)
|
||||||
|
|
||||||
// form request
|
// form request
|
||||||
var req v2reputation.AnnounceIntermediateResultRequest
|
var req v2reputation.AnnounceIntermediateResultRequest
|
||||||
|
|
34
reputation/doc.go
Normal file
34
reputation/doc.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Package reputation collects functionality related to the NeoFS reputation system.
|
||||||
|
|
||||||
|
The functionality is based on the system described in the NeoFS specification.
|
||||||
|
|
||||||
|
Trust type represents simple instances of trust values. PeerToPeerTrust extends
|
||||||
|
Trust to support the direction of trust, i.e. from whom to whom. GlobalTrust
|
||||||
|
is designed as a global measure of trust in a network member. See the docs
|
||||||
|
for each type for details.
|
||||||
|
|
||||||
|
Instances can be also used to process NeoFS API V2 protocol messages
|
||||||
|
(see neo.fs.v2.reputation package in https://github.com/nspcc-dev/neofs-api).
|
||||||
|
|
||||||
|
On client side:
|
||||||
|
import "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
|
|
||||||
|
var msg reputation.GlobalTrust
|
||||||
|
trust.WriteToV2(&msg)
|
||||||
|
|
||||||
|
// send trust
|
||||||
|
|
||||||
|
On server side:
|
||||||
|
// recv msg
|
||||||
|
|
||||||
|
var trust reputation.GlobalTrust
|
||||||
|
trust.ReadFromV2(msg)
|
||||||
|
|
||||||
|
// process trust
|
||||||
|
|
||||||
|
Using package types in an application is recommended to potentially work with
|
||||||
|
different protocol versions with which these types are compatible.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package reputation
|
|
@ -2,82 +2,102 @@ package reputation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
"github.com/mr-tron/base58"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
"github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerID represents peer ID compatible with NeoFS API v2.
|
// PeerID represents unique identifier of the peer participating in the NeoFS
|
||||||
type PeerID reputation.PeerID
|
// reputation system.
|
||||||
|
|
||||||
// NewPeerID creates and returns blank PeerID.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.PeerID
|
||||||
// - publicKey: nil.
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
func NewPeerID() *PeerID {
|
|
||||||
return PeerIDFromV2(new(reputation.PeerID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerIDFromV2 converts NeoFS API v2 reputation.PeerID message to PeerID.
|
|
||||||
//
|
//
|
||||||
// Nil reputation.PeerID converts to nil.
|
// Instances can be created using built-in var declaration.
|
||||||
func PeerIDFromV2(id *reputation.PeerID) *PeerID {
|
type PeerID struct {
|
||||||
return (*PeerID)(id)
|
m reputation.PeerID
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPublicKey sets peer ID as a compressed public key.
|
// ReadFromV2 reads PeerID from the reputation.PeerID message. Returns an
|
||||||
func (x *PeerID) SetPublicKey(v [33]byte) {
|
// error if the message is malformed according to the NeoFS API V2 protocol.
|
||||||
(*reputation.PeerID)(x).SetPublicKey(v[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts PeerID to NeoFS API v2 reputation.PeerID message.
|
|
||||||
//
|
//
|
||||||
// Nil PeerID converts to nil.
|
// See also WriteToV2.
|
||||||
func (x *PeerID) ToV2() *reputation.PeerID {
|
func (x *PeerID) ReadFromV2(m reputation.PeerID) error {
|
||||||
return (*reputation.PeerID)(x)
|
val := m.GetPublicKey()
|
||||||
}
|
if len(val) == 0 {
|
||||||
|
return errors.New("missing ID bytes")
|
||||||
// Equal returns true if identifiers are identical.
|
|
||||||
func (x *PeerID) Equal(x2 *PeerID) bool {
|
|
||||||
return bytes.Equal(
|
|
||||||
(*reputation.PeerID)(x).GetPublicKey(),
|
|
||||||
(*reputation.PeerID)(x2).GetPublicKey(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses PeerID from base58 string.
|
|
||||||
func (x *PeerID) Parse(s string) error {
|
|
||||||
data, err := base58.Decode(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*reputation.PeerID)(x).SetPublicKey(data)
|
x.m = m
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns base58 string representation of PeerID.
|
// WriteToV2 writes PeerID to the reputation.PeerID message.
|
||||||
func (x *PeerID) String() string {
|
// The message must not be nil.
|
||||||
return base58.Encode((*reputation.PeerID)(x).GetPublicKey())
|
//
|
||||||
|
// See also ReadFromV2.
|
||||||
|
func (x PeerID) WriteToV2(m *reputation.PeerID) {
|
||||||
|
*m = x.m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal marshals PeerID into a protobuf binary form.
|
// SetPublicKey sets PeerID as a binary-encoded public key which authenticates
|
||||||
func (x *PeerID) Marshal() ([]byte, error) {
|
// the participant of the NeoFS reputation system.
|
||||||
return (*reputation.PeerID)(x).StableMarshal(nil), nil
|
//
|
||||||
|
// Argument MUST NOT be mutated, make a copy first.
|
||||||
|
//
|
||||||
|
// See also CompareKey.
|
||||||
|
func (x *PeerID) SetPublicKey(key []byte) {
|
||||||
|
x.m.SetPublicKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of PeerID.
|
// PublicKey return public key set using SetPublicKey.
|
||||||
func (x *PeerID) Unmarshal(data []byte) error {
|
//
|
||||||
return (*reputation.PeerID)(x).Unmarshal(data)
|
// Zero PeerID has zero key which is incorrect according to NeoFS API
|
||||||
|
// protocol.
|
||||||
|
//
|
||||||
|
// Return value MUST NOT be mutated, make a copy first.
|
||||||
|
func (x PeerID) PublicKey() []byte {
|
||||||
|
return x.m.GetPublicKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON encodes PeerID to protobuf JSON format.
|
// ComparePeerKey checks if the given PeerID corresponds to the party
|
||||||
func (x *PeerID) MarshalJSON() ([]byte, error) {
|
// authenticated by the given binary public key.
|
||||||
return (*reputation.PeerID)(x).MarshalJSON()
|
func ComparePeerKey(peer PeerID, key []byte) bool {
|
||||||
|
return bytes.Equal(peer.PublicKey(), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON decodes PeerID from protobuf JSON format.
|
// EncodeToString encodes ID into NeoFS API protocol string.
|
||||||
func (x *PeerID) UnmarshalJSON(data []byte) error {
|
//
|
||||||
return (*reputation.PeerID)(x).UnmarshalJSON(data)
|
// Zero PeerID is base58 encoding of PeerIDSize zeros.
|
||||||
|
//
|
||||||
|
// See also DecodeString.
|
||||||
|
func (x PeerID) EncodeToString() string {
|
||||||
|
return base58.Encode(x.m.GetPublicKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes string into PeerID according to NeoFS API protocol.
|
||||||
|
// Returns an error if s is malformed.
|
||||||
|
//
|
||||||
|
// See also DecodeString.
|
||||||
|
func (x *PeerID) DecodeString(s string) error {
|
||||||
|
data, err := base58.Decode(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode base58: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m.SetPublicKey(data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
//
|
||||||
|
// String is designed to be human-readable, and its format MAY differ between
|
||||||
|
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||||
|
// be used to encode ID into NeoFS protocol string.
|
||||||
|
func (x PeerID) String() string {
|
||||||
|
return x.EncodeToString()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,86 +3,38 @@ package reputation_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
reputationV2 "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
||||||
reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test"
|
reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPeerID_ToV2(t *testing.T) {
|
func TestPeerID_PublicKey(t *testing.T) {
|
||||||
t.Run("nil", func(t *testing.T) {
|
var val reputation.PeerID
|
||||||
var x *reputation.PeerID
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
require.Zero(t, val.PublicKey())
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
key := []byte{3, 2, 1}
|
||||||
peerID := reputationtest.PeerID()
|
|
||||||
|
|
||||||
require.Equal(t, peerID, reputation.PeerIDFromV2(peerID.ToV2()))
|
val.SetPublicKey(key)
|
||||||
})
|
|
||||||
|
var m v2reputation.PeerID
|
||||||
|
val.WriteToV2(&m)
|
||||||
|
|
||||||
|
require.Equal(t, key, m.GetPublicKey())
|
||||||
|
|
||||||
|
var val2 reputation.PeerID
|
||||||
|
require.NoError(t, val2.ReadFromV2(m))
|
||||||
|
|
||||||
|
require.Equal(t, key, val.PublicKey())
|
||||||
|
|
||||||
|
require.True(t, reputation.ComparePeerKey(val, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerID_String(t *testing.T) {
|
func TestPeerID_EncodeToString(t *testing.T) {
|
||||||
t.Run("Parse/String", func(t *testing.T) {
|
val := reputationtest.PeerID()
|
||||||
id := reputationtest.PeerID()
|
var val2 reputation.PeerID
|
||||||
|
|
||||||
strID := id.String()
|
require.NoError(t, val2.DecodeString(val.EncodeToString()))
|
||||||
|
require.Equal(t, val, val2)
|
||||||
id2 := reputation.NewPeerID()
|
|
||||||
|
|
||||||
err := id2.Parse(strID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, id, id2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
id := reputation.NewPeerID()
|
|
||||||
|
|
||||||
require.Empty(t, id.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerIDEncoding(t *testing.T) {
|
|
||||||
id := reputationtest.PeerID()
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
data, err := id.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
id2 := reputation.NewPeerID()
|
|
||||||
require.NoError(t, id2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, id, id2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
|
||||||
data, err := id.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
id2 := reputation.NewPeerID()
|
|
||||||
require.NoError(t, id2.UnmarshalJSON(data))
|
|
||||||
|
|
||||||
require.Equal(t, id, id2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerIDFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *reputationV2.PeerID
|
|
||||||
|
|
||||||
require.Nil(t, reputation.PeerIDFromV2(x))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewPeerID(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
id := reputation.NewPeerID()
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
idV2 := id.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, idV2.GetPublicKey())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
package reputationtest
|
package reputationtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func PeerID() *reputation.PeerID {
|
func PeerID() (v reputation.PeerID) {
|
||||||
v := reputation.NewPeerID()
|
|
||||||
|
|
||||||
p, err := keys.NewPrivateKey()
|
p, err := keys.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key := [33]byte{}
|
v.SetPublicKey(p.PublicKey().Bytes())
|
||||||
copy(key[:], p.Bytes())
|
|
||||||
v.SetPublicKey(key)
|
|
||||||
|
|
||||||
return v
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Trust() *reputation.Trust {
|
func Trust() (v reputation.Trust) {
|
||||||
v := reputation.NewTrust()
|
|
||||||
v.SetPeer(PeerID())
|
v.SetPeer(PeerID())
|
||||||
v.SetValue(1.5)
|
v.SetValue(0.5)
|
||||||
|
|
||||||
return v
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeerToPeerTrust() *reputation.PeerToPeerTrust {
|
func PeerToPeerTrust() (v reputation.PeerToPeerTrust) {
|
||||||
v := reputation.NewPeerToPeerTrust()
|
|
||||||
v.SetTrustingPeer(PeerID())
|
v.SetTrustingPeer(PeerID())
|
||||||
v.SetTrust(Trust())
|
v.SetTrust(Trust())
|
||||||
|
|
||||||
return v
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GlobalTrust() *reputation.GlobalTrust {
|
func GlobalTrust() (v reputation.GlobalTrust) {
|
||||||
v := reputation.NewGlobalTrust()
|
v.Init()
|
||||||
v.SetManager(PeerID())
|
v.SetManager(PeerID())
|
||||||
v.SetTrust(Trust())
|
v.SetTrust(Trust())
|
||||||
|
|
||||||
return v
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func SignedGlobalTrust(t testing.TB) *reputation.GlobalTrust {
|
func SignedGlobalTrust() reputation.GlobalTrust {
|
||||||
gt := GlobalTrust()
|
gt := GlobalTrust()
|
||||||
|
|
||||||
p, err := keys.NewPrivateKey()
|
p, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
if err != nil {
|
||||||
require.NoError(t, gt.Sign(&p.PrivateKey))
|
panic(fmt.Sprintf("unexpected error from key creator: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gt.Sign(neofsecdsa.Signer(p.PrivateKey))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from GlobalTrust.Sign: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
return gt
|
return gt
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,327 +1,416 @@
|
||||||
package reputation
|
package reputation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
"github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Trust represents peer's trust compatible with NeoFS API v2.
|
// Trust represents quantitative assessment of the trust of a participant in the
|
||||||
type Trust reputation.Trust
|
// NeoFS reputation system.
|
||||||
|
|
||||||
// NewTrust creates and returns blank Trust.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// Trust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.Trust
|
||||||
// - value: 0;
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
// - PeerID: nil.
|
//
|
||||||
func NewTrust() *Trust {
|
// Instances can be created using built-in var declaration.
|
||||||
return TrustFromV2(new(reputation.Trust))
|
type Trust struct {
|
||||||
|
m reputation.Trust
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrustFromV2 converts NeoFS API v2
|
// ReadFromV2 reads Trust from the reputation.Trust message. Returns an
|
||||||
// reputation.Trust message structure to Trust.
|
// error if the message is malformed according to the NeoFS API V2 protocol.
|
||||||
//
|
//
|
||||||
// Nil reputation.Trust converts to nil.
|
// See also WriteToV2.
|
||||||
func TrustFromV2(t *reputation.Trust) *Trust {
|
func (x *Trust) ReadFromV2(m reputation.Trust) error {
|
||||||
return (*Trust)(t)
|
if val := m.GetValue(); val < 0 || val > 1 {
|
||||||
|
return fmt.Errorf("invalid trust value %v", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerV2 := m.GetPeer()
|
||||||
|
if peerV2 == nil {
|
||||||
|
return errors.New("missing peer field")
|
||||||
|
}
|
||||||
|
|
||||||
|
var peer PeerID
|
||||||
|
|
||||||
|
err := peer.ReadFromV2(*peerV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid peer field: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m = m
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts Trust to NeoFS API v2
|
// WriteToV2 writes Trust to the reputation.Trust message.
|
||||||
// reputation.Trust message structure.
|
// The message must not be nil.
|
||||||
//
|
//
|
||||||
// Nil Trust converts to nil.
|
// See also ReadFromV2.
|
||||||
func (x *Trust) ToV2() *reputation.Trust {
|
func (x Trust) WriteToV2(m *reputation.Trust) {
|
||||||
return (*reputation.Trust)(x)
|
*m = x.m
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrustsToV2 converts slice of Trust's to slice of
|
// SetPeer specifies identifier of the participant of the NeoFS reputation system
|
||||||
// NeoFS API v2 reputation.Trust message structures.
|
// to which the Trust relates.
|
||||||
func TrustsToV2(xs []Trust) (res []reputation.Trust) {
|
//
|
||||||
if xs != nil {
|
// See also Peer.
|
||||||
res = make([]reputation.Trust, len(xs))
|
func (x *Trust) SetPeer(id PeerID) {
|
||||||
|
var m reputation.PeerID
|
||||||
|
id.WriteToV2(&m)
|
||||||
|
|
||||||
for i := range xs {
|
x.m.SetPeer(&m)
|
||||||
res[i] = *xs[i].ToV2()
|
}
|
||||||
|
|
||||||
|
// Peer returns peer identifier set using SetPeer.
|
||||||
|
//
|
||||||
|
// Zero Trust returns zero PeerID which is incorrect according to the NeoFS API
|
||||||
|
// protocol.
|
||||||
|
func (x Trust) Peer() (res PeerID) {
|
||||||
|
m := x.m.GetPeer()
|
||||||
|
if m != nil {
|
||||||
|
err := res.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPeer sets trusted peer ID.
|
// SetValue sets the Trust value. Value MUST be in range [0;1].
|
||||||
func (x *Trust) SetPeer(id *PeerID) {
|
//
|
||||||
(*reputation.Trust)(x).SetPeer(id.ToV2())
|
// See also Value.
|
||||||
}
|
|
||||||
|
|
||||||
// Peer returns trusted peer ID.
|
|
||||||
func (x *Trust) Peer() *PeerID {
|
|
||||||
return PeerIDFromV2(
|
|
||||||
(*reputation.Trust)(x).GetPeer())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue sets trust value.
|
|
||||||
func (x *Trust) SetValue(val float64) {
|
func (x *Trust) SetValue(val float64) {
|
||||||
(*reputation.Trust)(x).SetValue(val)
|
if val < 0 || val > 1 {
|
||||||
}
|
panic(fmt.Sprintf("trust value is out-of-range %v", val))
|
||||||
|
|
||||||
// Value returns trust value.
|
|
||||||
func (x *Trust) Value() float64 {
|
|
||||||
return (*reputation.Trust)(x).GetValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals Trust into a protobuf binary form.
|
|
||||||
func (x *Trust) Marshal() ([]byte, error) {
|
|
||||||
return (*reputation.Trust)(x).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of Trust.
|
|
||||||
func (x *Trust) Unmarshal(data []byte) error {
|
|
||||||
return (*reputation.Trust)(x).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes Trust to protobuf JSON format.
|
|
||||||
func (x *Trust) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*reputation.Trust)(x).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes Trust from protobuf JSON format.
|
|
||||||
func (x *Trust) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*reputation.Trust)(x).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerToPeerTrust represents directed peer-to-peer trust
|
|
||||||
// compatible with NeoFS API v2.
|
|
||||||
type PeerToPeerTrust reputation.PeerToPeerTrust
|
|
||||||
|
|
||||||
// NewPeerToPeerTrust creates and returns blank PeerToPeerTrust.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - trusting: nil;
|
|
||||||
// - trust: nil.
|
|
||||||
func NewPeerToPeerTrust() *PeerToPeerTrust {
|
|
||||||
return PeerToPeerTrustFromV2(new(reputation.PeerToPeerTrust))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PeerToPeerTrustFromV2 converts NeoFS API v2
|
|
||||||
// reputation.PeerToPeerTrust message structure to PeerToPeerTrust.
|
|
||||||
//
|
|
||||||
// Nil reputation.PeerToPeerTrust converts to nil.
|
|
||||||
func PeerToPeerTrustFromV2(t *reputation.PeerToPeerTrust) *PeerToPeerTrust {
|
|
||||||
return (*PeerToPeerTrust)(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts PeerToPeerTrust to NeoFS API v2
|
|
||||||
// reputation.PeerToPeerTrust message structure.
|
|
||||||
//
|
|
||||||
// Nil PeerToPeerTrust converts to nil.
|
|
||||||
func (x *PeerToPeerTrust) ToV2() *reputation.PeerToPeerTrust {
|
|
||||||
return (*reputation.PeerToPeerTrust)(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTrustingPeer sets trusting peer ID.
|
|
||||||
func (x *PeerToPeerTrust) SetTrustingPeer(id *PeerID) {
|
|
||||||
(*reputation.PeerToPeerTrust)(x).SetTrustingPeer(id.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrustingPeer returns trusting peer ID.
|
|
||||||
func (x *PeerToPeerTrust) TrustingPeer() *PeerID {
|
|
||||||
return PeerIDFromV2(
|
|
||||||
(*reputation.PeerToPeerTrust)(x).
|
|
||||||
GetTrustingPeer(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTrust sets trust value of the trusting peer to the trusted one.
|
|
||||||
func (x *PeerToPeerTrust) SetTrust(t *Trust) {
|
|
||||||
(*reputation.PeerToPeerTrust)(x).SetTrust(t.ToV2())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trust returns trust value of the trusting peer to the trusted one.
|
|
||||||
func (x *PeerToPeerTrust) Trust() *Trust {
|
|
||||||
return TrustFromV2(
|
|
||||||
(*reputation.PeerToPeerTrust)(x).GetTrust())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal marshals PeerToPeerTrust into a protobuf binary form.
|
|
||||||
func (x *PeerToPeerTrust) Marshal() ([]byte, error) {
|
|
||||||
return (*reputation.PeerToPeerTrust)(x).StableMarshal(nil), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of PeerToPeerTrust.
|
|
||||||
func (x *PeerToPeerTrust) Unmarshal(data []byte) error {
|
|
||||||
return (*reputation.PeerToPeerTrust)(x).Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes PeerToPeerTrust to protobuf JSON format.
|
|
||||||
func (x *PeerToPeerTrust) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*reputation.PeerToPeerTrust)(x).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes PeerToPeerTrust from protobuf JSON format.
|
|
||||||
func (x *PeerToPeerTrust) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*reputation.PeerToPeerTrust)(x).UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalTrust represents peer's global trust compatible with NeoFS API v2.
|
|
||||||
type GlobalTrust reputation.GlobalTrust
|
|
||||||
|
|
||||||
// NewGlobalTrust creates and returns blank GlobalTrust.
|
|
||||||
//
|
|
||||||
// Defaults:
|
|
||||||
// - version: version.Current();
|
|
||||||
// - manager: nil;
|
|
||||||
// - trust: nil.
|
|
||||||
func NewGlobalTrust() *GlobalTrust {
|
|
||||||
gt := GlobalTrustFromV2(new(reputation.GlobalTrust))
|
|
||||||
ver := version.Current()
|
|
||||||
gt.SetVersion(&ver)
|
|
||||||
|
|
||||||
return gt
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalTrustFromV2 converts NeoFS API v2
|
|
||||||
// reputation.GlobalTrust message structure to GlobalTrust.
|
|
||||||
//
|
|
||||||
// Nil reputation.GlobalTrust converts to nil.
|
|
||||||
func GlobalTrustFromV2(t *reputation.GlobalTrust) *GlobalTrust {
|
|
||||||
return (*GlobalTrust)(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts GlobalTrust to NeoFS API v2
|
|
||||||
// reputation.GlobalTrust message structure.
|
|
||||||
//
|
|
||||||
// Nil GlobalTrust converts to nil.
|
|
||||||
func (x *GlobalTrust) ToV2() *reputation.GlobalTrust {
|
|
||||||
return (*reputation.GlobalTrust)(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVersion sets GlobalTrust's protocol version.
|
|
||||||
func (x *GlobalTrust) SetVersion(version *version.Version) {
|
|
||||||
var verV2 refs.Version
|
|
||||||
version.WriteToV2(&verV2)
|
|
||||||
(*reputation.GlobalTrust)(x).SetVersion(&verV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns GlobalTrust's protocol version.
|
|
||||||
func (x *GlobalTrust) Version() *version.Version {
|
|
||||||
var ver version.Version
|
|
||||||
if verV2 := (*reputation.GlobalTrust)(x).GetVersion(); verV2 != nil {
|
|
||||||
ver.ReadFromV2(*verV2)
|
|
||||||
}
|
}
|
||||||
return &ver
|
|
||||||
|
x.m.SetValue(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns value set using SetValue.
|
||||||
|
//
|
||||||
|
// Zero Trust has zero value.
|
||||||
|
func (x Trust) Value() float64 {
|
||||||
|
return x.m.GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeerToPeerTrust represents trust of one participant of the NeoFS reputation
|
||||||
|
// system to another one.
|
||||||
|
//
|
||||||
|
// Trust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.PeerToPeerTrust
|
||||||
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
|
//
|
||||||
|
// Instances can be created using built-in var declaration.
|
||||||
|
type PeerToPeerTrust struct {
|
||||||
|
m reputation.PeerToPeerTrust
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromV2 reads PeerToPeerTrust from the reputation.PeerToPeerTrust message.
|
||||||
|
// Returns an error if the message is malformed according to the NeoFS API V2
|
||||||
|
// protocol.
|
||||||
|
//
|
||||||
|
// See also WriteToV2.
|
||||||
|
func (x *PeerToPeerTrust) ReadFromV2(m reputation.PeerToPeerTrust) error {
|
||||||
|
trustingV2 := m.GetTrustingPeer()
|
||||||
|
if trustingV2 == nil {
|
||||||
|
return errors.New("missing trusting peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var trusting PeerID
|
||||||
|
|
||||||
|
err := trusting.ReadFromV2(*trustingV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid trusting peer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustV2 := m.GetTrust()
|
||||||
|
if trustV2 == nil {
|
||||||
|
return errors.New("missing trust")
|
||||||
|
}
|
||||||
|
|
||||||
|
var trust Trust
|
||||||
|
|
||||||
|
err = trust.ReadFromV2(*trustV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid trust: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m = m
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToV2 writes PeerToPeerTrust to the reputation.PeerToPeerTrust message.
|
||||||
|
// The message must not be nil.
|
||||||
|
//
|
||||||
|
// See also ReadFromV2.
|
||||||
|
func (x PeerToPeerTrust) WriteToV2(m *reputation.PeerToPeerTrust) {
|
||||||
|
*m = x.m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrustingPeer specifies the peer from which trust comes in terms of the
|
||||||
|
// NeoFS reputation system.
|
||||||
|
//
|
||||||
|
// See also TrustingPeer.
|
||||||
|
func (x *PeerToPeerTrust) SetTrustingPeer(id PeerID) {
|
||||||
|
var m reputation.PeerID
|
||||||
|
id.WriteToV2(&m)
|
||||||
|
|
||||||
|
x.m.SetTrustingPeer(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustingPeer returns peer set using SetTrustingPeer.
|
||||||
|
//
|
||||||
|
// Zero PeerToPeerTrust has no trusting peer which is incorrect according
|
||||||
|
// to the NeoFS API protocol.
|
||||||
|
func (x PeerToPeerTrust) TrustingPeer() (res PeerID) {
|
||||||
|
m := x.m.GetTrustingPeer()
|
||||||
|
if m != nil {
|
||||||
|
err := res.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from PeerID.ReadFromV2: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrust sets trust value of the trusting peer to another participant
|
||||||
|
// of the NeoFS reputation system.
|
||||||
|
//
|
||||||
|
// See also Trust.
|
||||||
|
func (x *PeerToPeerTrust) SetTrust(t Trust) {
|
||||||
|
var tV2 reputation.Trust
|
||||||
|
t.WriteToV2(&tV2)
|
||||||
|
|
||||||
|
x.m.SetTrust(&tV2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trust returns trust set using SetTrust.
|
||||||
|
//
|
||||||
|
// Zero PeerToPeerTrust returns zero Trust which is incorect according to the
|
||||||
|
// NeoFS API protocol.
|
||||||
|
func (x PeerToPeerTrust) Trust() (res Trust) {
|
||||||
|
m := x.m.GetTrust()
|
||||||
|
if m != nil {
|
||||||
|
err := res.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Trust.ReadFromV2: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalTrust represents the final assessment of trust in the participant of
|
||||||
|
// the NeoFS reputation system obtained taking into account all other participants.
|
||||||
|
//
|
||||||
|
// GlobalTrust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.GlobalTrust
|
||||||
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
|
//
|
||||||
|
// Instances can be created using built-in var declaration.
|
||||||
|
type GlobalTrust struct {
|
||||||
|
m reputation.GlobalTrust
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFromV2 reads GlobalTrust from the reputation.GlobalTrust message.
|
||||||
|
// Returns an error if the message is malformed according to the NeoFS API V2
|
||||||
|
// protocol.
|
||||||
|
//
|
||||||
|
// See also WriteToV2.
|
||||||
|
func (x *GlobalTrust) ReadFromV2(m reputation.GlobalTrust) error {
|
||||||
|
if m.GetVersion() == nil {
|
||||||
|
return errors.New("missing version")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.GetSignature() == nil {
|
||||||
|
return errors.New("missing signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
body := m.GetBody()
|
||||||
|
if body == nil {
|
||||||
|
return errors.New("missing body")
|
||||||
|
}
|
||||||
|
|
||||||
|
managerV2 := body.GetManager()
|
||||||
|
if managerV2 == nil {
|
||||||
|
return errors.New("missing manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
var manager PeerID
|
||||||
|
|
||||||
|
err := manager.ReadFromV2(*managerV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid manager: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustV2 := body.GetTrust()
|
||||||
|
if trustV2 == nil {
|
||||||
|
return errors.New("missing trust")
|
||||||
|
}
|
||||||
|
|
||||||
|
var trust Trust
|
||||||
|
|
||||||
|
err = trust.ReadFromV2(*trustV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid trust: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.m = m
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToV2 writes GlobalTrust to the reputation.GlobalTrust message.
|
||||||
|
// The message must not be nil.
|
||||||
|
//
|
||||||
|
// See also ReadFromV2.
|
||||||
|
func (x GlobalTrust) WriteToV2(m *reputation.GlobalTrust) {
|
||||||
|
*m = x.m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes all internal data of the GlobalTrust required by NeoFS API
|
||||||
|
// protocol. Init MUST be called when creating a new global trust instance.
|
||||||
|
// Init SHOULD NOT be called multiple times. Init SHOULD NOT be called if
|
||||||
|
// the GlobalTrust instance is used for decoding only.
|
||||||
|
func (x *GlobalTrust) Init() {
|
||||||
|
var ver refs.Version
|
||||||
|
version.Current().WriteToV2(&ver)
|
||||||
|
|
||||||
|
x.m.SetVersion(&ver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GlobalTrust) setBodyField(setter func(*reputation.GlobalTrustBody)) {
|
func (x *GlobalTrust) setBodyField(setter func(*reputation.GlobalTrustBody)) {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
v2 := (*reputation.GlobalTrust)(x)
|
body := x.m.GetBody()
|
||||||
|
|
||||||
body := v2.GetBody()
|
|
||||||
if body == nil {
|
if body == nil {
|
||||||
body = new(reputation.GlobalTrustBody)
|
body = new(reputation.GlobalTrustBody)
|
||||||
v2.SetBody(body)
|
x.m.SetBody(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
setter(body)
|
setter(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetManager sets node manager ID.
|
// SetManager sets identifier of the NeoFS reputation system's participant which
|
||||||
func (x *GlobalTrust) SetManager(id *PeerID) {
|
// performed trust estimation.
|
||||||
|
//
|
||||||
|
// See also Manager.
|
||||||
|
func (x *GlobalTrust) SetManager(id PeerID) {
|
||||||
|
var m reputation.PeerID
|
||||||
|
id.WriteToV2(&m)
|
||||||
|
|
||||||
x.setBodyField(func(body *reputation.GlobalTrustBody) {
|
x.setBodyField(func(body *reputation.GlobalTrustBody) {
|
||||||
body.SetManager(id.ToV2())
|
body.SetManager(&m)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager returns node manager ID.
|
// Manager returns peer set using SetManager.
|
||||||
func (x *GlobalTrust) Manager() *PeerID {
|
//
|
||||||
return PeerIDFromV2(
|
// Zero GlobalTrust has zero manager which is incorrect according to the
|
||||||
(*reputation.GlobalTrust)(x).
|
// NeoFS API protocol.
|
||||||
GetBody().
|
func (x GlobalTrust) Manager() (res PeerID) {
|
||||||
GetManager(),
|
m := x.m.GetBody().GetManager()
|
||||||
)
|
if m != nil {
|
||||||
}
|
err := res.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
// SetTrust sets global trust value.
|
panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err))
|
||||||
func (x *GlobalTrust) SetTrust(trust *Trust) {
|
}
|
||||||
x.setBodyField(func(body *reputation.GlobalTrustBody) {
|
|
||||||
body.SetTrust(trust.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trust returns global trust value.
|
|
||||||
func (x *GlobalTrust) Trust() *Trust {
|
|
||||||
return TrustFromV2(
|
|
||||||
(*reputation.GlobalTrust)(x).
|
|
||||||
GetBody().
|
|
||||||
GetTrust(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs global trust value with key.
|
|
||||||
func (x *GlobalTrust) Sign(key *ecdsa.PrivateKey) error {
|
|
||||||
if key == nil {
|
|
||||||
return errors.New("nil private key")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := (*reputation.GlobalTrust)(x)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrust sets the global trust score of the network to a specific network
|
||||||
|
// member.
|
||||||
|
//
|
||||||
|
// See also Trust.
|
||||||
|
func (x *GlobalTrust) SetTrust(trust Trust) {
|
||||||
|
var m reputation.Trust
|
||||||
|
trust.WriteToV2(&m)
|
||||||
|
|
||||||
|
x.setBodyField(func(body *reputation.GlobalTrustBody) {
|
||||||
|
body.SetTrust(&m)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trust returns trust set using SetTrust.
|
||||||
|
//
|
||||||
|
// Zero GlobalTrust return zero Trust which is incorrect according to the
|
||||||
|
// NeoFS API protocol.
|
||||||
|
func (x GlobalTrust) Trust() (res Trust) {
|
||||||
|
m := x.m.GetBody().GetTrust()
|
||||||
|
if m != nil {
|
||||||
|
err := res.ReadFromV2(*m)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign calculates and writes signature of the GlobalTrust data. Returns
|
||||||
|
// signature calculation errors.
|
||||||
|
//
|
||||||
|
// Zero GlobalTrust is unsigned.
|
||||||
|
//
|
||||||
|
// Note that any GlobalTrust mutation is likely to break the signature, so it is
|
||||||
|
// expected to be calculated as a final stage of GlobalTrust formation.
|
||||||
|
//
|
||||||
|
// See also VerifySignature.
|
||||||
|
func (x *GlobalTrust) Sign(signer neofscrypto.Signer) error {
|
||||||
var sig neofscrypto.Signature
|
var sig neofscrypto.Signature
|
||||||
|
|
||||||
err := sig.Calculate(neofsecdsa.Signer(*key), m.GetBody().StableMarshal(nil))
|
err := sig.Calculate(signer, x.m.GetBody().StableMarshal(nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calculate signature: %w", err)
|
return fmt.Errorf("calculate signature: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sigv2 refs.Signature
|
var sigv2 refs.Signature
|
||||||
|
|
||||||
sig.WriteToV2(&sigv2)
|
sig.WriteToV2(&sigv2)
|
||||||
|
|
||||||
m.SetSignature(&sigv2)
|
x.m.SetSignature(&sigv2)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifySignature verifies global trust signature.
|
// VerifySignature checks if GlobalTrust signature is presented and valid.
|
||||||
func (x *GlobalTrust) VerifySignature() error {
|
//
|
||||||
m := (*reputation.GlobalTrust)(x)
|
// Zero GlobalTrust fails the check.
|
||||||
|
//
|
||||||
sigV2 := m.GetSignature()
|
// See also Sign.
|
||||||
|
func (x GlobalTrust) VerifySignature() bool {
|
||||||
|
sigV2 := x.m.GetSignature()
|
||||||
if sigV2 == nil {
|
if sigV2 == nil {
|
||||||
return errors.New("missing signature")
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var sig neofscrypto.Signature
|
var sig neofscrypto.Signature
|
||||||
sig.ReadFromV2(*sigV2)
|
sig.ReadFromV2(*sigV2)
|
||||||
|
|
||||||
if !sig.Verify(m.GetBody().StableMarshal(nil)) {
|
return sig.Verify(x.m.GetBody().StableMarshal(nil))
|
||||||
return errors.New("wrong signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal marshals GlobalTrust into a protobuf binary form.
|
// Marshal encodes GlobalTrust into a binary format of the NeoFS API protocol
|
||||||
func (x *GlobalTrust) Marshal() ([]byte, error) {
|
// (Protocol Buffers with direct field order).
|
||||||
return (*reputation.GlobalTrust)(x).StableMarshal(nil), nil
|
//
|
||||||
|
// See also Unmarshal.
|
||||||
|
func (x GlobalTrust) Marshal() []byte {
|
||||||
|
return x.m.StableMarshal(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of GlobalTrust.
|
// Unmarshal decodes NeoFS API protocol binary format into the GlobalTrust
|
||||||
|
// (Protocol Buffers with direct field order). Returns an error describing
|
||||||
|
// a format violation.
|
||||||
|
//
|
||||||
|
// See also Marshal.
|
||||||
func (x *GlobalTrust) Unmarshal(data []byte) error {
|
func (x *GlobalTrust) Unmarshal(data []byte) error {
|
||||||
return (*reputation.GlobalTrust)(x).Unmarshal(data)
|
return x.m.Unmarshal(data)
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes GlobalTrust to protobuf JSON format.
|
|
||||||
func (x *GlobalTrust) MarshalJSON() ([]byte, error) {
|
|
||||||
return (*reputation.GlobalTrust)(x).MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes GlobalTrust from protobuf JSON format.
|
|
||||||
func (x *GlobalTrust) UnmarshalJSON(data []byte) error {
|
|
||||||
return (*reputation.GlobalTrust)(x).UnmarshalJSON(data)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,248 +3,206 @@ package reputation_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
reputationV2 "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
reputationtestV2 "github.com/nspcc-dev/neofs-api-go/v2/reputation/test"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
||||||
reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test"
|
reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTrust(t *testing.T) {
|
func TestTrust_Peer(t *testing.T) {
|
||||||
trust := reputation.NewTrust()
|
var trust reputation.Trust
|
||||||
|
|
||||||
id := reputationtest.PeerID()
|
require.Zero(t, trust.Peer())
|
||||||
trust.SetPeer(id)
|
|
||||||
require.Equal(t, id, trust.Peer())
|
|
||||||
|
|
||||||
val := 1.5
|
trust = reputationtest.Trust()
|
||||||
trust.SetValue(val)
|
|
||||||
require.Equal(t, val, trust.Value())
|
|
||||||
|
|
||||||
t.Run("binary encoding", func(t *testing.T) {
|
peer := reputationtest.PeerID()
|
||||||
trust := reputationtest.Trust()
|
|
||||||
data, err := trust.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
trust2 := reputation.NewTrust()
|
trust.SetPeer(peer)
|
||||||
require.NoError(t, trust2.Unmarshal(data))
|
|
||||||
require.Equal(t, trust, trust2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("JSON encoding", func(t *testing.T) {
|
var peerV2 v2reputation.PeerID
|
||||||
trust := reputationtest.Trust()
|
peer.WriteToV2(&peerV2)
|
||||||
data, err := trust.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
trust2 := reputation.NewTrust()
|
var trustV2 v2reputation.Trust
|
||||||
require.NoError(t, trust2.UnmarshalJSON(data))
|
trust.WriteToV2(&trustV2)
|
||||||
require.Equal(t, trust, trust2)
|
|
||||||
})
|
require.Equal(t, &peerV2, trustV2.GetPeer())
|
||||||
|
|
||||||
|
var val2 reputation.Trust
|
||||||
|
require.NoError(t, val2.ReadFromV2(trustV2))
|
||||||
|
|
||||||
|
require.Equal(t, peer, val2.Peer())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeerToPeerTrust(t *testing.T) {
|
func TestTrust_Value(t *testing.T) {
|
||||||
t.Run("v2", func(t *testing.T) {
|
var val reputation.Trust
|
||||||
p2ptV2 := reputationtestV2.GeneratePeerToPeerTrust(false)
|
|
||||||
|
|
||||||
p2pt := reputation.PeerToPeerTrustFromV2(p2ptV2)
|
require.Zero(t, val.Value())
|
||||||
|
|
||||||
require.Equal(t, p2ptV2, p2pt.ToV2())
|
val = reputationtest.Trust()
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("getters+setters", func(t *testing.T) {
|
const value = 0.75
|
||||||
p2pt := reputation.NewPeerToPeerTrust()
|
|
||||||
|
|
||||||
require.Nil(t, p2pt.TrustingPeer())
|
val.SetValue(value)
|
||||||
require.Nil(t, p2pt.Trust())
|
|
||||||
|
|
||||||
trusting := reputationtest.PeerID()
|
var trustV2 v2reputation.Trust
|
||||||
p2pt.SetTrustingPeer(trusting)
|
val.WriteToV2(&trustV2)
|
||||||
require.Equal(t, trusting, p2pt.TrustingPeer())
|
|
||||||
|
require.EqualValues(t, value, trustV2.GetValue())
|
||||||
|
|
||||||
|
var val2 reputation.Trust
|
||||||
|
require.NoError(t, val2.ReadFromV2(trustV2))
|
||||||
|
|
||||||
|
require.EqualValues(t, value, val2.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeerToPeerTrust_TrustingPeer(t *testing.T) {
|
||||||
|
var val reputation.PeerToPeerTrust
|
||||||
|
|
||||||
|
require.Zero(t, val.TrustingPeer())
|
||||||
|
|
||||||
|
val = reputationtest.PeerToPeerTrust()
|
||||||
|
|
||||||
|
peer := reputationtest.PeerID()
|
||||||
|
|
||||||
|
val.SetTrustingPeer(peer)
|
||||||
|
|
||||||
|
var peerV2 v2reputation.PeerID
|
||||||
|
peer.WriteToV2(&peerV2)
|
||||||
|
|
||||||
|
var trustV2 v2reputation.PeerToPeerTrust
|
||||||
|
val.WriteToV2(&trustV2)
|
||||||
|
|
||||||
|
require.Equal(t, &peerV2, trustV2.GetTrustingPeer())
|
||||||
|
|
||||||
|
var val2 reputation.PeerToPeerTrust
|
||||||
|
require.NoError(t, val2.ReadFromV2(trustV2))
|
||||||
|
|
||||||
|
require.Equal(t, peer, val2.TrustingPeer())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeerToPeerTrust_Trust(t *testing.T) {
|
||||||
|
var val reputation.PeerToPeerTrust
|
||||||
|
|
||||||
|
require.Zero(t, val.Trust())
|
||||||
|
|
||||||
|
val = reputationtest.PeerToPeerTrust()
|
||||||
|
|
||||||
trust := reputationtest.Trust()
|
trust := reputationtest.Trust()
|
||||||
p2pt.SetTrust(trust)
|
|
||||||
require.Equal(t, trust, p2pt.Trust())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("encoding", func(t *testing.T) {
|
val.SetTrust(trust)
|
||||||
p2pt := reputationtest.PeerToPeerTrust()
|
|
||||||
|
var trustV2 v2reputation.Trust
|
||||||
|
trust.WriteToV2(&trustV2)
|
||||||
|
|
||||||
|
var valV2 v2reputation.PeerToPeerTrust
|
||||||
|
val.WriteToV2(&valV2)
|
||||||
|
|
||||||
|
require.Equal(t, &trustV2, valV2.GetTrust())
|
||||||
|
|
||||||
|
var val2 reputation.PeerToPeerTrust
|
||||||
|
require.NoError(t, val2.ReadFromV2(valV2))
|
||||||
|
|
||||||
|
require.Equal(t, trust, val2.Trust())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalTrust_Init(t *testing.T) {
|
||||||
|
var val reputation.GlobalTrust
|
||||||
|
val.Init()
|
||||||
|
|
||||||
|
var valV2 v2reputation.GlobalTrust
|
||||||
|
val.WriteToV2(&valV2)
|
||||||
|
|
||||||
|
var verV2 refs.Version
|
||||||
|
version.Current().WriteToV2(&verV2)
|
||||||
|
|
||||||
|
require.Equal(t, &verV2, valV2.GetVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalTrust_Manager(t *testing.T) {
|
||||||
|
var val reputation.GlobalTrust
|
||||||
|
|
||||||
|
require.Zero(t, val.Manager())
|
||||||
|
|
||||||
|
val = reputationtest.SignedGlobalTrust()
|
||||||
|
|
||||||
|
peer := reputationtest.PeerID()
|
||||||
|
|
||||||
|
val.SetManager(peer)
|
||||||
|
|
||||||
|
var peerV2 v2reputation.PeerID
|
||||||
|
peer.WriteToV2(&peerV2)
|
||||||
|
|
||||||
|
var trustV2 v2reputation.GlobalTrust
|
||||||
|
val.WriteToV2(&trustV2)
|
||||||
|
|
||||||
|
require.Equal(t, &peerV2, trustV2.GetBody().GetManager())
|
||||||
|
|
||||||
|
var val2 reputation.GlobalTrust
|
||||||
|
require.NoError(t, val2.ReadFromV2(trustV2))
|
||||||
|
|
||||||
|
require.Equal(t, peer, val2.Manager())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalTrust_Trust(t *testing.T) {
|
||||||
|
var val reputation.GlobalTrust
|
||||||
|
|
||||||
|
require.Zero(t, val.Trust())
|
||||||
|
|
||||||
|
val = reputationtest.SignedGlobalTrust()
|
||||||
|
|
||||||
|
trust := reputationtest.Trust()
|
||||||
|
|
||||||
|
val.SetTrust(trust)
|
||||||
|
|
||||||
|
var trustV2 v2reputation.Trust
|
||||||
|
trust.WriteToV2(&trustV2)
|
||||||
|
|
||||||
|
var valV2 v2reputation.GlobalTrust
|
||||||
|
val.WriteToV2(&valV2)
|
||||||
|
|
||||||
|
require.Equal(t, &trustV2, valV2.GetBody().GetTrust())
|
||||||
|
|
||||||
|
var val2 reputation.GlobalTrust
|
||||||
|
require.NoError(t, val2.ReadFromV2(valV2))
|
||||||
|
|
||||||
|
require.Equal(t, trust, val2.Trust())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalTrust_Sign(t *testing.T) {
|
||||||
|
k, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
val := reputationtest.GlobalTrust()
|
||||||
|
|
||||||
|
require.False(t, val.VerifySignature())
|
||||||
|
|
||||||
|
require.NoError(t, val.Sign(neofsecdsa.Signer(k.PrivateKey)))
|
||||||
|
|
||||||
|
var valV2 v2reputation.GlobalTrust
|
||||||
|
val.WriteToV2(&valV2)
|
||||||
|
|
||||||
|
require.NotZero(t, valV2.GetSignature())
|
||||||
|
|
||||||
|
var val2 reputation.GlobalTrust
|
||||||
|
require.NoError(t, val2.ReadFromV2(valV2))
|
||||||
|
|
||||||
|
require.True(t, val2.VerifySignature())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalTrustEncoding(t *testing.T) {
|
||||||
|
val := reputationtest.SignedGlobalTrust()
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
t.Run("binary", func(t *testing.T) {
|
||||||
data, err := p2pt.Marshal()
|
data := val.Marshal()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p2pt2 := reputation.NewPeerToPeerTrust()
|
var val2 reputation.GlobalTrust
|
||||||
require.NoError(t, p2pt2.Unmarshal(data))
|
require.NoError(t, val2.Unmarshal(data))
|
||||||
require.Equal(t, p2pt, p2pt2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("JSON", func(t *testing.T) {
|
require.Equal(t, val, val2)
|
||||||
data, err := p2pt.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
p2pt2 := reputation.NewPeerToPeerTrust()
|
|
||||||
require.NoError(t, p2pt2.UnmarshalJSON(data))
|
|
||||||
require.Equal(t, p2pt, p2pt2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobalTrust(t *testing.T) {
|
|
||||||
t.Run("v2", func(t *testing.T) {
|
|
||||||
gtV2 := reputationtestV2.GenerateGlobalTrust(false)
|
|
||||||
|
|
||||||
gt := reputation.GlobalTrustFromV2(gtV2)
|
|
||||||
|
|
||||||
require.Equal(t, gtV2, gt.ToV2())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("getters+setters", func(t *testing.T) {
|
|
||||||
gt := reputation.NewGlobalTrust()
|
|
||||||
|
|
||||||
require.Equal(t, version.Current(), *gt.Version())
|
|
||||||
require.Nil(t, gt.Manager())
|
|
||||||
require.Nil(t, gt.Trust())
|
|
||||||
|
|
||||||
var ver version.Version
|
|
||||||
ver.SetMajor(13)
|
|
||||||
ver.SetMinor(31)
|
|
||||||
gt.SetVersion(&ver)
|
|
||||||
require.Equal(t, ver, *gt.Version())
|
|
||||||
|
|
||||||
mngr := reputationtest.PeerID()
|
|
||||||
gt.SetManager(mngr)
|
|
||||||
require.Equal(t, mngr, gt.Manager())
|
|
||||||
|
|
||||||
trust := reputationtest.Trust()
|
|
||||||
gt.SetTrust(trust)
|
|
||||||
require.Equal(t, trust, gt.Trust())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("sign+verify", func(t *testing.T) {
|
|
||||||
gt := reputationtest.SignedGlobalTrust(t)
|
|
||||||
|
|
||||||
err := gt.VerifySignature()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("encoding", func(t *testing.T) {
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
|
||||||
gt := reputationtest.SignedGlobalTrust(t)
|
|
||||||
|
|
||||||
data, err := gt.Marshal()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gt2 := reputation.NewGlobalTrust()
|
|
||||||
require.NoError(t, gt2.Unmarshal(data))
|
|
||||||
require.Equal(t, gt, gt2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("JSON", func(t *testing.T) {
|
|
||||||
gt := reputationtest.SignedGlobalTrust(t)
|
|
||||||
data, err := gt.MarshalJSON()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gt2 := reputation.NewGlobalTrust()
|
|
||||||
require.NoError(t, gt2.UnmarshalJSON(data))
|
|
||||||
require.Equal(t, gt, gt2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrustFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *reputationV2.Trust
|
|
||||||
|
|
||||||
require.Nil(t, reputation.TrustFromV2(x))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerToPeerTrustFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *reputationV2.PeerToPeerTrust
|
|
||||||
|
|
||||||
require.Nil(t, reputation.PeerToPeerTrustFromV2(x))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobalTrustFromV2(t *testing.T) {
|
|
||||||
t.Run("from nil", func(t *testing.T) {
|
|
||||||
var x *reputationV2.GlobalTrust
|
|
||||||
|
|
||||||
require.Nil(t, reputation.GlobalTrustFromV2(x))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrust_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *reputation.Trust
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeerToPeerTrust_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *reputation.PeerToPeerTrust
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobalTrust_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *reputation.GlobalTrust
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewTrust(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
trust := reputation.NewTrust()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Zero(t, trust.Value())
|
|
||||||
require.Nil(t, trust.Peer())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
trustV2 := trust.ToV2()
|
|
||||||
|
|
||||||
require.Zero(t, trustV2.GetValue())
|
|
||||||
require.Nil(t, trustV2.GetPeer())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewPeerToPeerTrust(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
trust := reputation.NewPeerToPeerTrust()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, trust.Trust())
|
|
||||||
require.Nil(t, trust.TrustingPeer())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
trustV2 := trust.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, trustV2.GetTrust())
|
|
||||||
require.Nil(t, trustV2.GetTrustingPeer())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGlobalTrust(t *testing.T) {
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
trust := reputation.NewGlobalTrust()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, trust.Manager())
|
|
||||||
require.Nil(t, trust.Trust())
|
|
||||||
|
|
||||||
require.Equal(t, version.Current(), *trust.Version())
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
trustV2 := trust.ToV2()
|
|
||||||
|
|
||||||
require.Nil(t, trustV2.GetBody())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue