From f25253738ac18a0cf7370c8d946b7393ff4843d6 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 23 Mar 2021 21:33:37 +0300 Subject: [PATCH] [#428] reputation: Implement local trust storage Implement in-memory `Storage` which is going to be used to submit the results of interactions with network members. `Storage` also provides an iterator interface, so the component can be used in `Controller`. Signed-off-by: Leonard Lyubich --- .../reputation/local/storage/calls.go | 173 ++++++++++++++++++ .../reputation/local/storage/storage.go | 43 +++++ 2 files changed, 216 insertions(+) create mode 100644 pkg/services/reputation/local/storage/calls.go create mode 100644 pkg/services/reputation/local/storage/storage.go diff --git a/pkg/services/reputation/local/storage/calls.go b/pkg/services/reputation/local/storage/calls.go new file mode 100644 index 000000000..04605378c --- /dev/null +++ b/pkg/services/reputation/local/storage/calls.go @@ -0,0 +1,173 @@ +package truststorage + +import ( + "errors" + "sync" + + "github.com/nspcc-dev/neofs-node/pkg/services/reputation" +) + +// UpdatePrm groups the parameters of Storage's Update operation. +type UpdatePrm struct { + sat bool + + epoch uint64 + + peer reputation.PeerID +} + +// SetEpoch sets number of the epoch +// when the interaction happened. +func (p *UpdatePrm) SetEpoch(e uint64) { + p.epoch = e +} + +// SetPeer sets identifier of the peer +// with which the local node interacted. +func (p *UpdatePrm) SetPeer(id reputation.PeerID) { + p.peer = id +} + +// SetSatisfactory sets successful completion status. +func (p *UpdatePrm) SetSatisfactory(sat bool) { + p.sat = sat +} + +type trustValue struct { + sat, all int +} + +// EpochTrustValueStorage represents storage of +// the trust values by particular epoch. +type EpochTrustValueStorage struct { + mtx sync.RWMutex + + mItems map[string]*trustValue +} + +func newTrustValueStorage() *EpochTrustValueStorage { + return &EpochTrustValueStorage{ + mItems: make(map[string]*trustValue, 1), + } +} + +func stringifyPeerID(id reputation.PeerID) string { + return string(id.Bytes()) +} + +func peerIDFromString(str string) reputation.PeerID { + return reputation.PeerIDFromBytes([]byte(str)) +} + +func (s *EpochTrustValueStorage) update(prm UpdatePrm) { + s.mtx.Lock() + + { + strID := stringifyPeerID(prm.peer) + + val, ok := s.mItems[strID] + if !ok { + val = new(trustValue) + s.mItems[strID] = val + } + + if prm.sat { + val.sat++ + } + + val.all++ + } + + s.mtx.Unlock() +} + +// Update updates the number of satisfactory transactions with peer. +func (s *Storage) Update(prm UpdatePrm) { + var trustStorage *EpochTrustValueStorage + + s.mtx.Lock() + + { + var ( + ok bool + epoch = prm.epoch + ) + + trustStorage, ok = s.mItems[epoch] + if !ok { + trustStorage = newTrustValueStorage() + s.mItems[epoch] = trustStorage + } + } + + s.mtx.Unlock() + + trustStorage.update(prm) +} + +// ErrNoPositiveTrust is returned by iterator when +// there is no positive number of successful transactions. +var ErrNoPositiveTrust = errors.New("no positive trust") + +// DataForEpoch returns EpochValueStorage for epoch. +// +// If there is no data for the epoch, ErrNoPositiveTrust returns. +func (s *Storage) DataForEpoch(epoch uint64) (*EpochTrustValueStorage, error) { + s.mtx.RLock() + trustStorage, ok := s.mItems[epoch] + s.mtx.RUnlock() + + if !ok { + return nil, ErrNoPositiveTrust + } + + return trustStorage, nil +} + +// Iterate iterates over normalized trust values and passes them to parameterized handler. +// +// Values are normalized according to http://ilpubs.stanford.edu:8090/562/1/2002-56.pdf Chapter 4.5. +// If divisor in formula is zero, ErrNoPositiveTrust returns. +func (s *EpochTrustValueStorage) Iterate(h reputation.TrustHandler) (err error) { + s.mtx.RLock() + + { + var ( + sum reputation.TrustValue + mVals = make(map[string]reputation.TrustValue, len(s.mItems)) + ) + + // iterate first time to calculate normalizing divisor + for strID, val := range s.mItems { + if val.all > 0 { + num := reputation.TrustValueFromInt(val.sat) + denom := reputation.TrustValueFromInt(val.all) + + v := num.Div(denom) + + mVals[strID] = v + + sum.Add(v) + } + } + + err = ErrNoPositiveTrust + + if !sum.IsZero() { + for strID, val := range mVals { + t := reputation.Trust{} + + t.SetPeer(peerIDFromString(strID)) + t.SetValue(val) + + if err = h(t); err != nil { + break + } + } + } + } + + s.mtx.RUnlock() + + return +} diff --git a/pkg/services/reputation/local/storage/storage.go b/pkg/services/reputation/local/storage/storage.go new file mode 100644 index 000000000..4007309d6 --- /dev/null +++ b/pkg/services/reputation/local/storage/storage.go @@ -0,0 +1,43 @@ +package truststorage + +import ( + "sync" +) + +// Prm groups the required parameters of the Storage's constructor. +// +// All values must comply with the requirements imposed on them. +// Passing incorrect parameter values will result in constructor +// failure (error or panic depending on the implementation). +// +// The component is not parameterizable at the moment. +type Prm struct{} + +// Storage represents in-memory storage of +// local reputation values. +// +// Storage provides access to normalized local trust +// values through iterator interface. +// +// For correct operation, Storage must be created +// using the constructor (New) based on the required parameters +// and optional components. After successful creation, +// Storage is immediately ready to work through API. +type Storage struct { + prm Prm + + mtx sync.RWMutex + + mItems map[uint64]*EpochTrustValueStorage +} + +// New creates a new instance of the Storage. +// +// The created Storage does not require additional +// initialization and is completely ready for work. +func New(prm Prm) *Storage { + return &Storage{ + prm: prm, + mItems: make(map[uint64]*EpochTrustValueStorage), + } +}