diff --git a/client/reputation.go b/client/reputation.go index c6806a59..35c211a7 100644 --- a/client/reputation.go +++ b/client/reputation.go @@ -65,10 +65,13 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru reqBody := new(v2reputation.AnnounceLocalTrustRequestBody) reqBody.SetEpoch(prm.epoch) - trusts := make([]reputation.Trust, len(prm.trusts)) - copy(trusts, prm.trusts) + trusts := make([]v2reputation.Trust, len(prm.trusts)) - reqBody.SetTrusts(reputation.TrustsToV2(trusts)) + for i := range prm.trusts { + prm.trusts[i].WriteToV2(&trusts[i]) + } + + reqBody.SetTrusts(trusts) // form request var req v2reputation.AnnounceLocalTrustRequest @@ -159,11 +162,14 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI panic("current trust value not set") } + var trust v2reputation.PeerToPeerTrust + prm.trust.WriteToV2(&trust) + // form request body reqBody := new(v2reputation.AnnounceIntermediateResultRequestBody) reqBody.SetEpoch(prm.epoch) reqBody.SetIteration(prm.iter) - reqBody.SetTrust(prm.trust.ToV2()) + reqBody.SetTrust(&trust) // form request var req v2reputation.AnnounceIntermediateResultRequest diff --git a/reputation/doc.go b/reputation/doc.go new file mode 100644 index 00000000..b4ce2d65 --- /dev/null +++ b/reputation/doc.go @@ -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 diff --git a/reputation/peer.go b/reputation/peer.go index d80d23a7..8d704fe5 100644 --- a/reputation/peer.go +++ b/reputation/peer.go @@ -2,82 +2,102 @@ package reputation import ( "bytes" + "errors" + "fmt" "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/v2/reputation" ) -// PeerID represents peer ID compatible with NeoFS API v2. -type PeerID reputation.PeerID - -// NewPeerID creates and returns blank PeerID. +// PeerID represents unique identifier of the peer participating in the NeoFS +// reputation system. // -// Defaults: -// - publicKey: nil. -func NewPeerID() *PeerID { - return PeerIDFromV2(new(reputation.PeerID)) -} - -// PeerIDFromV2 converts NeoFS API v2 reputation.PeerID message to PeerID. +// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.PeerID +// message. See ReadFromV2 / WriteToV2 methods. // -// Nil reputation.PeerID converts to nil. -func PeerIDFromV2(id *reputation.PeerID) *PeerID { - return (*PeerID)(id) +// Instances can be created using built-in var declaration. +type PeerID struct { + m reputation.PeerID } -// SetPublicKey sets peer ID as a compressed public key. -func (x *PeerID) SetPublicKey(v [33]byte) { - (*reputation.PeerID)(x).SetPublicKey(v[:]) -} - -// ToV2 converts PeerID to NeoFS API v2 reputation.PeerID message. +// ReadFromV2 reads PeerID from the reputation.PeerID message. Returns an +// error if the message is malformed according to the NeoFS API V2 protocol. // -// Nil PeerID converts to nil. -func (x *PeerID) ToV2() *reputation.PeerID { - return (*reputation.PeerID)(x) -} - -// 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 +// See also WriteToV2. +func (x *PeerID) ReadFromV2(m reputation.PeerID) error { + val := m.GetPublicKey() + if len(val) == 0 { + return errors.New("missing ID bytes") } - (*reputation.PeerID)(x).SetPublicKey(data) + x.m = m return nil } -// String returns base58 string representation of PeerID. -func (x *PeerID) String() string { - return base58.Encode((*reputation.PeerID)(x).GetPublicKey()) +// WriteToV2 writes PeerID to the reputation.PeerID message. +// The message must not be nil. +// +// See also ReadFromV2. +func (x PeerID) WriteToV2(m *reputation.PeerID) { + *m = x.m } -// Marshal marshals PeerID into a protobuf binary form. -func (x *PeerID) Marshal() ([]byte, error) { - return (*reputation.PeerID)(x).StableMarshal(nil), nil +// SetPublicKey sets PeerID as a binary-encoded public key which authenticates +// the participant of the NeoFS reputation system. +// +// 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. -func (x *PeerID) Unmarshal(data []byte) error { - return (*reputation.PeerID)(x).Unmarshal(data) +// PublicKey return public key set using SetPublicKey. +// +// 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. -func (x *PeerID) MarshalJSON() ([]byte, error) { - return (*reputation.PeerID)(x).MarshalJSON() +// ComparePeerKey checks if the given PeerID corresponds to the party +// authenticated by the given binary public key. +func ComparePeerKey(peer PeerID, key []byte) bool { + return bytes.Equal(peer.PublicKey(), key) } -// UnmarshalJSON decodes PeerID from protobuf JSON format. -func (x *PeerID) UnmarshalJSON(data []byte) error { - return (*reputation.PeerID)(x).UnmarshalJSON(data) +// EncodeToString encodes ID into NeoFS API protocol string. +// +// 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() } diff --git a/reputation/peer_test.go b/reputation/peer_test.go index 3bed03d4..bfee237a 100644 --- a/reputation/peer_test.go +++ b/reputation/peer_test.go @@ -3,86 +3,38 @@ package reputation_test import ( "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" reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test" "github.com/stretchr/testify/require" ) -func TestPeerID_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *reputation.PeerID +func TestPeerID_PublicKey(t *testing.T) { + var val reputation.PeerID - require.Nil(t, x.ToV2()) - }) + require.Zero(t, val.PublicKey()) - t.Run("nil", func(t *testing.T) { - peerID := reputationtest.PeerID() + key := []byte{3, 2, 1} - 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) { - t.Run("Parse/String", func(t *testing.T) { - id := reputationtest.PeerID() +func TestPeerID_EncodeToString(t *testing.T) { + val := reputationtest.PeerID() + var val2 reputation.PeerID - strID := id.String() - - 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()) - }) + require.NoError(t, val2.DecodeString(val.EncodeToString())) + require.Equal(t, val, val2) } diff --git a/reputation/test/generate.go b/reputation/test/generate.go index 622254f0..cdde4072 100644 --- a/reputation/test/generate.go +++ b/reputation/test/generate.go @@ -1,58 +1,58 @@ package reputationtest import ( - "testing" + "fmt" "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/stretchr/testify/require" ) -func PeerID() *reputation.PeerID { - v := reputation.NewPeerID() - +func PeerID() (v reputation.PeerID) { p, err := keys.NewPrivateKey() if err != nil { panic(err) } - key := [33]byte{} - copy(key[:], p.Bytes()) - v.SetPublicKey(key) + v.SetPublicKey(p.PublicKey().Bytes()) - return v + return } -func Trust() *reputation.Trust { - v := reputation.NewTrust() +func Trust() (v reputation.Trust) { v.SetPeer(PeerID()) - v.SetValue(1.5) + v.SetValue(0.5) - return v + return } -func PeerToPeerTrust() *reputation.PeerToPeerTrust { - v := reputation.NewPeerToPeerTrust() +func PeerToPeerTrust() (v reputation.PeerToPeerTrust) { v.SetTrustingPeer(PeerID()) v.SetTrust(Trust()) - return v + return } -func GlobalTrust() *reputation.GlobalTrust { - v := reputation.NewGlobalTrust() +func GlobalTrust() (v reputation.GlobalTrust) { + v.Init() v.SetManager(PeerID()) v.SetTrust(Trust()) - return v + return } -func SignedGlobalTrust(t testing.TB) *reputation.GlobalTrust { +func SignedGlobalTrust() reputation.GlobalTrust { gt := GlobalTrust() p, err := keys.NewPrivateKey() - require.NoError(t, err) - require.NoError(t, gt.Sign(&p.PrivateKey)) + if err != nil { + 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 } diff --git a/reputation/trust.go b/reputation/trust.go index 4a73f752..346996ce 100644 --- a/reputation/trust.go +++ b/reputation/trust.go @@ -1,327 +1,416 @@ package reputation import ( - "crypto/ecdsa" "errors" "fmt" "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/reputation" 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" ) -// Trust represents peer's trust compatible with NeoFS API v2. -type Trust reputation.Trust - -// NewTrust creates and returns blank Trust. +// Trust represents quantitative assessment of the trust of a participant in the +// NeoFS reputation system. // -// Defaults: -// - value: 0; -// - PeerID: nil. -func NewTrust() *Trust { - return TrustFromV2(new(reputation.Trust)) +// Trust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.Trust +// message. See ReadFromV2 / WriteToV2 methods. +// +// Instances can be created using built-in var declaration. +type Trust struct { + m reputation.Trust } -// TrustFromV2 converts NeoFS API v2 -// reputation.Trust message structure to Trust. +// ReadFromV2 reads Trust from the reputation.Trust message. Returns an +// error if the message is malformed according to the NeoFS API V2 protocol. // -// Nil reputation.Trust converts to nil. -func TrustFromV2(t *reputation.Trust) *Trust { - return (*Trust)(t) +// See also WriteToV2. +func (x *Trust) ReadFromV2(m reputation.Trust) error { + 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 -// reputation.Trust message structure. +// WriteToV2 writes Trust to the reputation.Trust message. +// The message must not be nil. // -// Nil Trust converts to nil. -func (x *Trust) ToV2() *reputation.Trust { - return (*reputation.Trust)(x) +// See also ReadFromV2. +func (x Trust) WriteToV2(m *reputation.Trust) { + *m = x.m } -// TrustsToV2 converts slice of Trust's to slice of -// NeoFS API v2 reputation.Trust message structures. -func TrustsToV2(xs []Trust) (res []reputation.Trust) { - if xs != nil { - res = make([]reputation.Trust, len(xs)) +// SetPeer specifies identifier of the participant of the NeoFS reputation system +// to which the Trust relates. +// +// See also Peer. +func (x *Trust) SetPeer(id PeerID) { + var m reputation.PeerID + id.WriteToV2(&m) - for i := range xs { - res[i] = *xs[i].ToV2() + x.m.SetPeer(&m) +} + +// 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 } -// SetPeer sets trusted peer ID. -func (x *Trust) SetPeer(id *PeerID) { - (*reputation.Trust)(x).SetPeer(id.ToV2()) -} - -// Peer returns trusted peer ID. -func (x *Trust) Peer() *PeerID { - return PeerIDFromV2( - (*reputation.Trust)(x).GetPeer()) -} - -// SetValue sets trust value. +// SetValue sets the Trust value. Value MUST be in range [0;1]. +// +// See also Value. func (x *Trust) SetValue(val float64) { - (*reputation.Trust)(x).SetValue(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) + if val < 0 || val > 1 { + panic(fmt.Sprintf("trust value is out-of-range %v", val)) } - 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)) { if x != nil { - v2 := (*reputation.GlobalTrust)(x) - - body := v2.GetBody() + body := x.m.GetBody() if body == nil { body = new(reputation.GlobalTrustBody) - v2.SetBody(body) + x.m.SetBody(body) } setter(body) } } -// SetManager sets node manager ID. -func (x *GlobalTrust) SetManager(id *PeerID) { +// SetManager sets identifier of the NeoFS reputation system's participant which +// 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) { - body.SetManager(id.ToV2()) + body.SetManager(&m) }) } -// Manager returns node manager ID. -func (x *GlobalTrust) Manager() *PeerID { - return PeerIDFromV2( - (*reputation.GlobalTrust)(x). - GetBody(). - GetManager(), - ) -} - -// SetTrust sets global trust value. -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") +// Manager returns peer set using SetManager. +// +// Zero GlobalTrust has zero manager which is incorrect according to the +// NeoFS API protocol. +func (x GlobalTrust) Manager() (res PeerID) { + m := x.m.GetBody().GetManager() + if m != nil { + err := res.ReadFromV2(*m) + if err != nil { + panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err)) + } } - 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 - err := sig.Calculate(neofsecdsa.Signer(*key), m.GetBody().StableMarshal(nil)) + err := sig.Calculate(signer, x.m.GetBody().StableMarshal(nil)) if err != nil { return fmt.Errorf("calculate signature: %w", err) } var sigv2 refs.Signature - sig.WriteToV2(&sigv2) - m.SetSignature(&sigv2) + x.m.SetSignature(&sigv2) return nil } -// VerifySignature verifies global trust signature. -func (x *GlobalTrust) VerifySignature() error { - m := (*reputation.GlobalTrust)(x) - - sigV2 := m.GetSignature() +// VerifySignature checks if GlobalTrust signature is presented and valid. +// +// Zero GlobalTrust fails the check. +// +// See also Sign. +func (x GlobalTrust) VerifySignature() bool { + sigV2 := x.m.GetSignature() if sigV2 == nil { - return errors.New("missing signature") + return false } var sig neofscrypto.Signature sig.ReadFromV2(*sigV2) - if !sig.Verify(m.GetBody().StableMarshal(nil)) { - return errors.New("wrong signature") - } - - return nil + return sig.Verify(x.m.GetBody().StableMarshal(nil)) } -// Marshal marshals GlobalTrust into a protobuf binary form. -func (x *GlobalTrust) Marshal() ([]byte, error) { - return (*reputation.GlobalTrust)(x).StableMarshal(nil), nil +// Marshal encodes GlobalTrust into a binary format of the NeoFS API protocol +// (Protocol Buffers with direct field order). +// +// 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 { - return (*reputation.GlobalTrust)(x).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) + return x.m.Unmarshal(data) } diff --git a/reputation/trust_test.go b/reputation/trust_test.go index 5c81ee56..b0b106c0 100644 --- a/reputation/trust_test.go +++ b/reputation/trust_test.go @@ -3,248 +3,206 @@ package reputation_test import ( "testing" - reputationV2 "github.com/nspcc-dev/neofs-api-go/v2/reputation" - reputationtestV2 "github.com/nspcc-dev/neofs-api-go/v2/reputation/test" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "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" reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/stretchr/testify/require" ) -func TestTrust(t *testing.T) { - trust := reputation.NewTrust() +func TestTrust_Peer(t *testing.T) { + var trust reputation.Trust - id := reputationtest.PeerID() - trust.SetPeer(id) - require.Equal(t, id, trust.Peer()) + require.Zero(t, trust.Peer()) - val := 1.5 - trust.SetValue(val) - require.Equal(t, val, trust.Value()) + trust = reputationtest.Trust() - t.Run("binary encoding", func(t *testing.T) { - trust := reputationtest.Trust() - data, err := trust.Marshal() - require.NoError(t, err) + peer := reputationtest.PeerID() - trust2 := reputation.NewTrust() - require.NoError(t, trust2.Unmarshal(data)) - require.Equal(t, trust, trust2) - }) + trust.SetPeer(peer) - t.Run("JSON encoding", func(t *testing.T) { - trust := reputationtest.Trust() - data, err := trust.MarshalJSON() - require.NoError(t, err) + var peerV2 v2reputation.PeerID + peer.WriteToV2(&peerV2) - trust2 := reputation.NewTrust() - require.NoError(t, trust2.UnmarshalJSON(data)) - require.Equal(t, trust, trust2) - }) + var trustV2 v2reputation.Trust + trust.WriteToV2(&trustV2) + + 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) { - t.Run("v2", func(t *testing.T) { - p2ptV2 := reputationtestV2.GeneratePeerToPeerTrust(false) +func TestTrust_Value(t *testing.T) { + var val reputation.Trust - 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) { - p2pt := reputation.NewPeerToPeerTrust() + const value = 0.75 - require.Nil(t, p2pt.TrustingPeer()) - require.Nil(t, p2pt.Trust()) + val.SetValue(value) - trusting := reputationtest.PeerID() - p2pt.SetTrustingPeer(trusting) - require.Equal(t, trusting, p2pt.TrustingPeer()) + var trustV2 v2reputation.Trust + val.WriteToV2(&trustV2) - trust := reputationtest.Trust() - p2pt.SetTrust(trust) - require.Equal(t, trust, p2pt.Trust()) - }) + require.EqualValues(t, value, trustV2.GetValue()) - t.Run("encoding", func(t *testing.T) { - p2pt := reputationtest.PeerToPeerTrust() + var val2 reputation.Trust + require.NoError(t, val2.ReadFromV2(trustV2)) - t.Run("binary", func(t *testing.T) { - data, err := p2pt.Marshal() - require.NoError(t, err) - - p2pt2 := reputation.NewPeerToPeerTrust() - require.NoError(t, p2pt2.Unmarshal(data)) - require.Equal(t, p2pt, p2pt2) - }) - - t.Run("JSON", func(t *testing.T) { - data, err := p2pt.MarshalJSON() - require.NoError(t, err) - - p2pt2 := reputation.NewPeerToPeerTrust() - require.NoError(t, p2pt2.UnmarshalJSON(data)) - require.Equal(t, p2pt, p2pt2) - }) - }) + require.EqualValues(t, value, val2.Value()) } -func TestGlobalTrust(t *testing.T) { - t.Run("v2", func(t *testing.T) { - gtV2 := reputationtestV2.GenerateGlobalTrust(false) +func TestPeerToPeerTrust_TrustingPeer(t *testing.T) { + var val reputation.PeerToPeerTrust - gt := reputation.GlobalTrustFromV2(gtV2) + require.Zero(t, val.TrustingPeer()) - require.Equal(t, gtV2, gt.ToV2()) - }) + val = reputationtest.PeerToPeerTrust() - t.Run("getters+setters", func(t *testing.T) { - gt := reputation.NewGlobalTrust() + peer := reputationtest.PeerID() - require.Equal(t, version.Current(), *gt.Version()) - require.Nil(t, gt.Manager()) - require.Nil(t, gt.Trust()) + val.SetTrustingPeer(peer) - var ver version.Version - ver.SetMajor(13) - ver.SetMinor(31) - gt.SetVersion(&ver) - require.Equal(t, ver, *gt.Version()) + var peerV2 v2reputation.PeerID + peer.WriteToV2(&peerV2) - mngr := reputationtest.PeerID() - gt.SetManager(mngr) - require.Equal(t, mngr, gt.Manager()) + var trustV2 v2reputation.PeerToPeerTrust + val.WriteToV2(&trustV2) - trust := reputationtest.Trust() - gt.SetTrust(trust) - require.Equal(t, trust, gt.Trust()) - }) + require.Equal(t, &peerV2, trustV2.GetTrustingPeer()) - t.Run("sign+verify", func(t *testing.T) { - gt := reputationtest.SignedGlobalTrust(t) + var val2 reputation.PeerToPeerTrust + require.NoError(t, val2.ReadFromV2(trustV2)) - 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) - }) - }) + require.Equal(t, peer, val2.TrustingPeer()) } -func TestTrustFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *reputationV2.Trust +func TestPeerToPeerTrust_Trust(t *testing.T) { + var val reputation.PeerToPeerTrust - require.Nil(t, reputation.TrustFromV2(x)) - }) + require.Zero(t, val.Trust()) + + val = reputationtest.PeerToPeerTrust() + + trust := reputationtest.Trust() + + val.SetTrust(trust) + + 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 TestPeerToPeerTrustFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *reputationV2.PeerToPeerTrust +func TestGlobalTrust_Init(t *testing.T) { + var val reputation.GlobalTrust + val.Init() - require.Nil(t, reputation.PeerToPeerTrustFromV2(x)) - }) + var valV2 v2reputation.GlobalTrust + val.WriteToV2(&valV2) + + var verV2 refs.Version + version.Current().WriteToV2(&verV2) + + require.Equal(t, &verV2, valV2.GetVersion()) } -func TestGlobalTrustFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *reputationV2.GlobalTrust +func TestGlobalTrust_Manager(t *testing.T) { + var val reputation.GlobalTrust - require.Nil(t, reputation.GlobalTrustFromV2(x)) - }) + 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 TestTrust_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *reputation.Trust +func TestGlobalTrust_Trust(t *testing.T) { + var val reputation.GlobalTrust - require.Nil(t, x.ToV2()) - }) + 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 TestPeerToPeerTrust_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *reputation.PeerToPeerTrust +func TestGlobalTrust_Sign(t *testing.T) { + k, err := keys.NewPrivateKey() + require.NoError(t, err) - require.Nil(t, x.ToV2()) - }) + 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 TestGlobalTrust_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *reputation.GlobalTrust +func TestGlobalTrustEncoding(t *testing.T) { + val := reputationtest.SignedGlobalTrust() - 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()) + t.Run("binary", func(t *testing.T) { + data := val.Marshal() + + var val2 reputation.GlobalTrust + require.NoError(t, val2.Unmarshal(data)) + + require.Equal(t, val, val2) }) }