Skip to content

Commit

Permalink
Performance enhancements: Flatten binding hierarchy. Now all bindings…
Browse files Browse the repository at this point in the history
… point to the long-term id

Signed-off-by: Alexandros Filios <[email protected]>
  • Loading branch information
alexandrosfilios committed Jan 16, 2025
1 parent 09cb7d7 commit 3897e91
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 217 deletions.
1 change: 1 addition & 0 deletions platform/common/driver/kvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type AuditInfoKVS interface {

type BindingKVS interface {
GetBinding(ephemeral view.Identity) (view.Identity, error)
HaveSameBinding(this, that view.Identity) (bool, error)
PutBinding(ephemeral, longTerm view.Identity) error
}

Expand Down
9 changes: 9 additions & 0 deletions platform/common/utils/collections/maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ func Values[K comparable, V any](m map[K]V) []V {
return res
}

func ContainsValue[K, V comparable](haystack map[K]V, needle V) bool {
for _, v := range haystack {
if v == needle {
return true
}
}
return false
}

func Keys[K comparable, V any](m map[K]V) []K {
res := make([]K, len(m))
i := 0
Expand Down
67 changes: 67 additions & 0 deletions platform/view/core/endpoint/binder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package endpoint

import (
"github.com/hyperledger-labs/fabric-smart-client/platform/common/driver"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
"github.com/pkg/errors"
)

type Binder interface {
GetLongTerm(ephemeral view.Identity) (view.Identity, error)
Bind(ephemeral, other view.Identity) error
IsBoundTo(this, that view.Identity) (bool, error)
}

func NewBinder(bindingKVS driver.BindingKVS) *binder {
return &binder{bindingKVS: bindingKVS}
}

type binder struct {
bindingKVS driver.BindingKVS
}

func (b *binder) Bind(this, that view.Identity) error {
if this.IsNone() || that.IsNone() {
return errors.New("empty ids passed")
}
// Make sure the long term is passed, so that the hierarchy is flat and all ephemerals point to the long term
longTerm, err := b.bindingKVS.GetBinding(that)
if err != nil {
return errors.Wrapf(err, "no long term found for [%s]. if long term was passed, it has to be registered first.", that)
}
if !longTerm.IsNone() {
logger.Debugf("Long term id for [%s] is [%s]", that, longTerm)
return b.bindingKVS.PutBinding(this, longTerm)
}

logger.Debugf("Id [%s] has no long term binding. It will be registered as a long-term id.", that)
if err := b.bindingKVS.PutBinding(that, that); err != nil {
return errors.Wrapf(err, "failed to register [%s] as long term", that)
}
err = b.bindingKVS.PutBinding(this, that)
if lt, err := b.bindingKVS.GetBinding(this); err != nil || lt.IsNone() {
logger.Errorf("wrong binding for [%s][%s]: %v", that, lt, err)
} else {
logger.Errorf("successful binding for [%s]", that)
}
return err
}

func (b *binder) RegisterLongTerm(longTerm view.Identity) error {
// Self reference that indicates that a binding is long term
return b.bindingKVS.PutBinding(longTerm, longTerm)
}

func (b *binder) GetLongTerm(ephemeral view.Identity) (view.Identity, error) {
return b.bindingKVS.GetBinding(ephemeral)
}

func (b *binder) IsBoundTo(this, that view.Identity) (bool, error) {
return b.bindingKVS.HaveSameBinding(this, that)
}
129 changes: 59 additions & 70 deletions platform/view/core/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
"strings"
"sync"

driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/common/driver"
"github.com/hyperledger-labs/fabric-smart-client/platform/common/services/logging"
"github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/collections"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/driver"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
"github.com/pkg/errors"
Expand All @@ -35,6 +35,14 @@ type Resolver struct {
IdentityGetter func() (view.Identity, []byte, error)
}

func (r *Resolver) GetName() string { return r.Name }

func (r *Resolver) GetId() view.Identity { return r.Id }

func (r *Resolver) GetAddress(port driver.PortName) string { return r.Addresses[port] }

func (r *Resolver) GetAddresses() map[driver.PortName]string { return r.Addresses }

func (r *Resolver) GetIdentity() (view.Identity, error) {
if r.IdentityGetter != nil {
id, _, err := r.IdentityGetter()
Expand All @@ -58,52 +66,53 @@ type Discovery interface {
type Service struct {
resolvers []*Resolver
resolversMutex sync.RWMutex
bindingKVS driver2.BindingKVS
binder Binder

pkiExtractorsLock sync.RWMutex
publicKeyExtractors []driver.PublicKeyExtractor
publicKeyIDSynthesizer driver.PublicKeyIDSynthesizer
}

// NewService returns a new instance of the view-sdk endpoint service
func NewService(bindingKVS driver2.BindingKVS) (*Service, error) {
func NewService(binder Binder) (*Service, error) {
er := &Service{
bindingKVS: bindingKVS,
binder: binder,
publicKeyExtractors: []driver.PublicKeyExtractor{},
publicKeyIDSynthesizer: DefaultPublicKeyIDSynthesizer{},
}
return er, nil
}

func (r *Service) Endpoint(party view.Identity) (map[driver.PortName]string, error) {
_, e, _, err := r.resolve(party)
return e, err
}

func (r *Service) Resolve(party view.Identity) (string, view.Identity, map[driver.PortName]string, []byte, error) {
cursor, e, resolver, err := r.resolve(party)
func (r *Service) Resolve(party view.Identity) (driver.Resolver, []byte, error) {
resolver, err := r.resolver(party)
if err != nil {
return "", nil, nil, nil, err
return nil, nil, err
}
return resolver.Name, cursor, e, r.pkiResolve(resolver), nil
return resolver, r.pkiResolve(resolver), nil
}

func (r *Service) resolve(party view.Identity) (view.Identity, map[driver.PortName]string, *Resolver, error) {
cursor := party
for {
// root endpoints have addresses
// is this a root endpoint
resolver, e, err := r.rootEndpoint(cursor)
if err == nil {
return cursor, e, resolver, nil
}
logger.Debugf("resolving via binding for %s", cursor)
cursor, err = r.bindingKVS.GetBinding(cursor)
if err != nil {
return nil, nil, nil, err
}
logger.Debugf("continue to [%s]", cursor)
func (r *Service) GetResolver(party view.Identity) (driver.Resolver, error) {
return r.resolver(party)
}

func (r *Service) resolver(party view.Identity) (*Resolver, error) {
// We can skip this check, but in case the long term was passed directly, this is going to spare us a DB lookup
resolver, err := r.rootEndpoint(party)
if err == nil {
return resolver, nil
}
logger.Debugf("resolving via binding for %s", party)
party, err = r.binder.GetLongTerm(party)
if err != nil {
return nil, err
}
logger.Debugf("continue to [%s]", party)
resolver, err = r.rootEndpoint(party)
if err != nil {
return nil, errors.Wrapf(err, "failed getting identity for [%s]", party)
}

return resolver, nil
}

func (r *Service) Bind(longTerm view.Identity, ephemeral view.Identity) error {
Expand All @@ -114,27 +123,19 @@ func (r *Service) Bind(longTerm view.Identity, ephemeral view.Identity) error {

logger.Debugf("bind [%s] to [%s]", ephemeral, longTerm)

if err := r.bindingKVS.PutBinding(ephemeral, longTerm); err != nil {
if err := r.binder.Bind(ephemeral, longTerm); err != nil {
return errors.WithMessagef(err, "failed storing binding of [%s] to [%s]", ephemeral.UniqueID(), longTerm.UniqueID())
}

return nil
}

func (r *Service) IsBoundTo(a view.Identity, b view.Identity) bool {
for {
if a.Equal(b) {
return true
}
next, err := r.bindingKVS.GetBinding(a)
if err != nil {
return false
}
if next.Equal(b) {
return true
}
a = next
ok, err := r.binder.IsBoundTo(a, b)
if err != nil {
logger.Errorf("error fetching entries [%s] and [%s]: %v", a, b, err)
}
return ok
}

func (r *Service) GetIdentity(endpoint string, pkID []byte) (view.Identity, error) {
Expand All @@ -143,37 +144,25 @@ func (r *Service) GetIdentity(endpoint string, pkID []byte) (view.Identity, erro

// search in the resolver list
for _, resolver := range r.resolvers {
resolverPKID := r.pkiResolve(resolver)
found := false
for _, addr := range resolver.Addresses {
if endpoint == addr {
found = true
break
}
}
if !found {
// check aliases
found = slices.Contains(resolver.Aliases, endpoint)
}
if endpoint == resolver.Name ||
found ||
endpoint == resolver.Name+"."+resolver.Domain ||
bytes.Equal(pkID, resolver.Id) ||
bytes.Equal(pkID, resolverPKID) {

id, err := resolver.GetIdentity()
if err != nil {
return nil, err
}
if logger.IsEnabledFor(zapcore.DebugLevel) {
logger.Debugf("resolving [%s,%s] to %s", endpoint, view.Identity(pkID), id)
}
return id, nil
if r.matchesResolver(endpoint, pkID, resolver) {
return resolver.GetIdentity()
}
}
return nil, errors.Errorf("identity not found at [%s,%s]", endpoint, view.Identity(pkID))
}

func (r *Service) matchesResolver(endpoint string, pkID []byte, resolver *Resolver) bool {
if len(endpoint) > 0 && (endpoint == resolver.Name ||
endpoint == resolver.Name+"."+resolver.Domain ||
collections.ContainsValue(resolver.Addresses, endpoint) ||
slices.Contains(resolver.Aliases, endpoint)) {
return true
}

return len(pkID) > 0 && (bytes.Equal(pkID, resolver.Id) ||
bytes.Equal(pkID, r.pkiResolve(resolver)))
}

func (r *Service) AddResolver(name string, domain string, addresses map[string]string, aliases []string, id []byte) (view.Identity, error) {
if logger.IsEnabledFor(zapcore.DebugLevel) {
logger.Debugf("adding resolver [%s,%s,%v,%v,%s]", name, domain, addresses, aliases, view.Identity(id).String())
Expand Down Expand Up @@ -265,17 +254,17 @@ func (r *Service) ExtractPKI(id []byte) []byte {
return nil
}

func (r *Service) rootEndpoint(party view.Identity) (*Resolver, map[driver.PortName]string, error) {
func (r *Service) rootEndpoint(party view.Identity) (*Resolver, error) {
r.resolversMutex.RLock()
defer r.resolversMutex.RUnlock()

for _, resolver := range r.resolvers {
if bytes.Equal(resolver.Id, party) {
return resolver, resolver.Addresses, nil
return resolver, nil
}
}

return nil, nil, errors.Errorf("endpoint not found for identity %s", party.UniqueID())
return nil, errors.Errorf("endpoint not found for identity %s", party.UniqueID())
}

var portNameMap = map[string]driver.PortName{
Expand Down
5 changes: 3 additions & 2 deletions platform/view/core/endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
type mockKVS struct{}

func (k mockKVS) GetBinding(ephemeral view.Identity) (view.Identity, error) { return nil, nil }
func (k mockKVS) HaveSameBinding(this, that view.Identity) (bool, error) { return false, nil }
func (k mockKVS) PutBinding(ephemeral, longTerm view.Identity) error { return nil }

type mockExtractor struct{}
Expand All @@ -26,7 +27,7 @@ func (m mockExtractor) ExtractPublicKey(id view.Identity) (any, error) {
}

func TestPKIResolveConcurrency(t *testing.T) {
svc, err := NewService(mockKVS{})
svc, err := NewService(NewBinder(mockKVS{}))
assert.NoError(err)

ext := mockExtractor{}
Expand All @@ -47,7 +48,7 @@ func TestPKIResolveConcurrency(t *testing.T) {
}

func TestGetIdentity(t *testing.T) {
svc, err := NewService(mockKVS{})
svc, err := NewService(NewBinder(mockKVS{}))
assert.NoError(err)

ext := mockExtractor{}
Expand Down
12 changes: 8 additions & 4 deletions platform/view/core/manager/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,19 +334,23 @@ func (ctx *ctx) Dispose() {
}

func (ctx *ctx) newSession(view view.View, contextID string, party view.Identity) (view.Session, error) {
_, _, endpoints, pkid, err := ctx.resolver.Resolve(party)
resolver, pkid, err := ctx.resolver.Resolve(party)
if err != nil {
return nil, err
}
return ctx.sessionFactory.NewSession(getIdentifier(view), contextID, endpoints[driver.P2PPort], pkid)
return ctx.sessionFactory.NewSession(getIdentifier(view), contextID, resolver.GetAddress(driver.P2PPort), pkid)
}

func (ctx *ctx) newSessionByID(sessionID, contextID string, party view.Identity) (view.Session, error) {
_, _, endpoints, pkid, err := ctx.resolver.Resolve(party)
resolver, pkid, err := ctx.resolver.Resolve(party)
if err != nil {
return nil, err
}
return ctx.sessionFactory.NewSessionWithID(sessionID, contextID, endpoints[driver.P2PPort], pkid, nil, nil)
var endpoint string
if resolver != nil {
endpoint = resolver.GetAddress(driver.P2PPort)
}
return ctx.sessionFactory.NewSessionWithID(sessionID, contextID, endpoint, pkid, nil, nil)
}

func (ctx *ctx) cleanup() {
Expand Down
12 changes: 10 additions & 2 deletions platform/view/driver/endpointservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,20 @@ type PublicKeyIDSynthesizer interface {

//go:generate counterfeiter -o mock/resolver.go -fake-name EndpointService . EndpointService

type Resolver interface {
GetName() string
GetId() view.Identity
GetAddress(port PortName) string
GetAddresses() map[PortName]string
}

// EndpointService models the endpoint service
type EndpointService interface {
// Resolve returns the identity the passed identity is bound to.
// It returns also: the endpoints and the pkiID
Resolve(party view.Identity) (string, view.Identity, map[PortName]string, []byte, error)

Resolve(party view.Identity) (Resolver, []byte, error)
// GetResolver returns the identity the passed identity is bound to
GetResolver(party view.Identity) (Resolver, error)
// GetIdentity returns an identity bound to either the passed label or public-key identifier.
GetIdentity(label string, pkiID []byte) (view.Identity, error)

Expand Down
Loading

0 comments on commit 3897e91

Please sign in to comment.