-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement relay url provider #1328
Changes from 10 commits
6b551f8
c44fce7
83c2623
4ed780c
ef66f75
b5b1303
4608ae5
e172aed
63131f0
0c3d309
1872c24
8415ecf
ee9b2ba
12d3ac1
31de2cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package relay | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
relayRegistryBindings "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDARelayRegistry" | ||
v2 "github.com/Layr-Labs/eigenda/core/v2" | ||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
gethcommon "github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
// DefaultRelayUrlProvider provides relay URL strings, based on relay key. | ||
type DefaultRelayUrlProvider struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: not sure how useful this abstraction is. It simply wraps the eth client to fetch relay info from chain. Could we just use EthClient straight from the relay client? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There are two reasons I thought it makes sense to create this abstraction:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Did you notice that there is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought you could have easily mocked this class, but I don't think that's true because you're passing in the client in the binding to generate the caller. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed to |
||
relayRegistryCaller *relayRegistryBindings.ContractEigenDARelayRegistryCaller | ||
} | ||
|
||
var _ RelayUrlProvider = &DefaultRelayUrlProvider{} | ||
|
||
// NewDefaultRelayUrlProvider constructs a DefaultRelayUrlProvider | ||
func NewDefaultRelayUrlProvider( | ||
ethClient common.EthClient, | ||
relayRegistryAddress gethcommon.Address, | ||
) (*DefaultRelayUrlProvider, error) { | ||
relayRegistryContractCaller, err := relayRegistryBindings.NewContractEigenDARelayRegistryCaller( | ||
relayRegistryAddress, ethClient) | ||
if err != nil { | ||
return nil, fmt.Errorf("NewContractEigenDARelayRegistryCaller: %w", err) | ||
} | ||
|
||
return &DefaultRelayUrlProvider{ | ||
relayRegistryCaller: relayRegistryContractCaller, | ||
}, nil | ||
} | ||
|
||
// GetRelayUrl gets the URL string for a given relayKey | ||
func (rup *DefaultRelayUrlProvider) GetRelayUrl(ctx context.Context, relayKey v2.RelayKey) (string, error) { | ||
relayUrl, err := rup.relayRegistryCaller.RelayKeyToUrl(&bind.CallOpts{Context: ctx}, relayKey) | ||
if err != nil { | ||
return "", fmt.Errorf("fetch relay key (%d) URL from EigenDARelayRegistry contract: %w", relayKey, err) | ||
} | ||
|
||
return relayUrl, nil | ||
} | ||
|
||
// GetRelayCount gets the number of relays that exist in the registry | ||
func (rup *DefaultRelayUrlProvider) GetRelayCount(ctx context.Context) (uint32, error) { | ||
// NextRelayKey initializes to 0, and is incremented each time a relay is added | ||
// current logic doesn't support removing relays, so NextRelayKey therefore corresponds directly to relay count | ||
relayCount, err := rup.relayRegistryCaller.NextRelayKey(&bind.CallOpts{Context: ctx}) | ||
if err != nil { | ||
return 0, fmt.Errorf("get next relay key from EigenDARelayRegistry contract: %w", err) | ||
} | ||
|
||
return relayCount, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package relay | ||
|
||
import ( | ||
"sync" | ||
) | ||
|
||
// KeyLock is a utility that provides a way to lock access to a given key of type T | ||
// | ||
// This utility is useful in situations where you want to synchronize operations for something that doesn't exist | ||
// in a concrete form. For example, perhaps you only want to create connections with a given peer on a single | ||
// thread of execution, but the new peer could appear simultaneously in concurrent operations. This utility allows | ||
// the first thread which encounters the new peer to perform necessary initialization tasks, and store generated | ||
// artifacts in a central location for subsequent callers to access. | ||
type KeyLock[T comparable] struct { | ||
// Map from key T to a mutex that corresponds to that key | ||
keyMutexMap map[T]*sync.Mutex | ||
anupsv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Used to lock access to the keyMutexMap, so that only a single mutex is created for each key | ||
globalMutex sync.Mutex | ||
} | ||
|
||
// NewKeyLock constructs a KeyLock utility | ||
func NewKeyLock[T comparable]() *KeyLock[T] { | ||
return &KeyLock[T]{ | ||
keyMutexMap: make(map[T]*sync.Mutex), | ||
} | ||
} | ||
|
||
// AcquireKeyLock acquires an exclusive lock on a conceptual key, and returns a function to release the lock | ||
func (kl *KeyLock[T]) AcquireKeyLock(key T) func() { | ||
// we must globally synchronize access to the mutex map, so that only a single mutex will be created for a given key | ||
kl.globalMutex.Lock() | ||
keyMutex, valueAlreadyExists := kl.keyMutexMap[key] | ||
if !valueAlreadyExists { | ||
keyMutex = &sync.Mutex{} | ||
kl.keyMutexMap[key] = keyMutex | ||
} | ||
kl.globalMutex.Unlock() | ||
|
||
keyMutex.Lock() | ||
return keyMutex.Unlock | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package relay | ||
|
||
import ( | ||
"sync" | ||
"sync/atomic" | ||
"testing" | ||
|
||
"github.com/Layr-Labs/eigenda/common/testutils/random" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestKeyLock(t *testing.T) { | ||
// test in a field of 100 unique keys | ||
keyCount := 100 | ||
|
||
// keep an atomic count, and a non-atomic count for each key | ||
// the atomic count can be used at the end of the test, to make sure that the non-atomic count was handled correctly | ||
atomicKeyAccessCounts := make([]atomic.Uint32, keyCount) | ||
nonAtomicKeyAccessCounts := make([]uint32, keyCount) | ||
for i := 0; i < keyCount; i++ { | ||
atomicKeyAccessCounts = append(atomicKeyAccessCounts, atomic.Uint32{}) | ||
nonAtomicKeyAccessCounts = append(nonAtomicKeyAccessCounts, uint32(0)) | ||
} | ||
|
||
keyLock := NewKeyLock[uint32]() | ||
|
||
var waitGroup sync.WaitGroup | ||
|
||
targetValue := uint32(1000) | ||
worker := func() { | ||
workerRandom := random.NewTestRandom() | ||
|
||
for { | ||
// randomly select a key to access | ||
keyToAccess := uint32(workerRandom.Intn(keyCount)) | ||
newValue := atomicKeyAccessCounts[keyToAccess].Add(1) | ||
|
||
unlock := keyLock.AcquireKeyLock(keyToAccess) | ||
// increment the non-atomic count after acquiring access | ||
// if the access controls are working correctly, this is a safe operation | ||
nonAtomicKeyAccessCounts[keyToAccess] = nonAtomicKeyAccessCounts[keyToAccess] + 1 | ||
unlock() | ||
|
||
// each worker stops looping after it sees a counter that has increased to targetValue | ||
if newValue >= targetValue { | ||
break | ||
} | ||
} | ||
|
||
waitGroup.Done() | ||
} | ||
|
||
// start up 100 concurrent workers | ||
for i := 0; i < 100; i++ { | ||
waitGroup.Add(1) | ||
go worker() | ||
} | ||
waitGroup.Wait() | ||
|
||
for i := 0; i < keyCount; i++ { | ||
require.Equal(t, atomicKeyAccessCounts[i].Load(), nonAtomicKeyAccessCounts[i]) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package relay | ||
|
||
import ( | ||
"context" | ||
|
||
v2 "github.com/Layr-Labs/eigenda/core/v2" | ||
) | ||
|
||
// RelayUrlProvider provides relay URL strings, based on relay key | ||
type RelayUrlProvider interface { | ||
// GetRelayUrl gets the URL string for a given relayKey | ||
GetRelayUrl(ctx context.Context, relayKey v2.RelayKey) (string, error) | ||
// GetRelayCount returns the number of relays in the registry | ||
GetRelayCount(ctx context.Context) (uint32, error) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package relay | ||
|
||
import ( | ||
"context" | ||
|
||
v2 "github.com/Layr-Labs/eigenda/core/v2" | ||
) | ||
|
||
// TestRelayUrlProvider implements RelayUrlProvider, for test cases | ||
// | ||
// NOT SAFE for concurrent use | ||
type TestRelayUrlProvider struct { | ||
urlMap map[v2.RelayKey]string | ||
} | ||
|
||
var _ RelayUrlProvider = &TestRelayUrlProvider{} | ||
|
||
func NewTestRelayUrlProvider() *TestRelayUrlProvider { | ||
return &TestRelayUrlProvider{ | ||
urlMap: make(map[v2.RelayKey]string), | ||
} | ||
} | ||
|
||
func (rup *TestRelayUrlProvider) GetRelayUrl(_ context.Context, relayKey v2.RelayKey) (string, error) { | ||
return rup.urlMap[relayKey], nil | ||
} | ||
|
||
func (rup *TestRelayUrlProvider) GetRelayCount(_ context.Context) (uint32, error) { | ||
return uint32(len(rup.urlMap)), nil | ||
} | ||
|
||
func (rup *TestRelayUrlProvider) StoreRelayUrl(relayKey v2.RelayKey, url string) { | ||
rup.urlMap[relayKey] = url | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: filename
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed