-
Notifications
You must be signed in to change notification settings - Fork 18
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
MX-14354: snapshotless observers support #388
MX-14354: snapshotless observers support #388
Conversation
# Conflicts: # process/accountProcessor.go
observer/holder/nodesHolder.go
Outdated
syncedNodesCache cacheType = "syncedNodes" | ||
outOfSyncNodesCache cacheType = "outOfSyncNodes" | ||
syncedFallbackCache cacheType = "syncedFallbackNodes" | ||
outOfSyncFallbackNodes cacheType = "outOfSyncFallbackNodes" |
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.
outOfSyncFallbackNodes cacheType = "outOfSyncFallbackNodes" | |
outOfSyncFallbackCache cacheType = "outOfSyncFallbackCache" |
for consistency
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.
right. renamed
observer/holder/nodesHolder.go
Outdated
func (nh *nodesHolder) getObservers(cache cacheType, shardID uint32) []*data.NodeData { | ||
cacheKey := getCacheKey(cache, shardID) | ||
nh.mut.RLock() | ||
cachedValues, exists := nh.cache[getCacheKey(cache, shardID)] |
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.
cachedValues, exists := nh.cache[getCacheKey(cache, shardID)] | |
cachedValues, exists := nh.cache[cacheKey] |
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.
right. fixed
observer/holder/nodesHolder.go
Outdated
if cache == syncedFallbackCache && isSynced && isFallback { | ||
return true | ||
} | ||
if cache == outOfSyncFallbackNodes && !isSynced && isFallback { | ||
return true | ||
} | ||
if cache == syncedNodesCache && isSynced && !isFallback { | ||
return true | ||
} | ||
if cache == outOfSyncNodesCache && !isSynced && !isFallback { | ||
return true | ||
} | ||
|
||
return false |
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.
if cache == syncedFallbackCache && isSynced && isFallback { | |
return true | |
} | |
if cache == outOfSyncFallbackNodes && !isSynced && isFallback { | |
return true | |
} | |
if cache == syncedNodesCache && isSynced && !isFallback { | |
return true | |
} | |
if cache == outOfSyncNodesCache && !isSynced && !isFallback { | |
return true | |
} | |
return false | |
return cache == syncedFallbackCache && isSynced && isFallback || | |
cache == outOfSyncFallbackNodes && !isSynced && isFallback || | |
cache == syncedNodesCache && isSynced && !isFallback || | |
cache == outOfSyncNodesCache && !isSynced && !isFallback |
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.
Although it reduces the code size, I do not like this approach is it is more cryptic in terms of understanding the code
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.
I would keep it as it is since it's easier to read and can be helpful for debugging (you can trace on which if
it returned true
// nodes not cached, compute the list and update the cache | ||
recomputedList := make([]*data.NodeData, 0) | ||
nh.mut.Lock() | ||
for _, node := range nh.allNodes[shardID] { |
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.
should we consider having here one extra check, just in case the list is already recomputed in between RUnlock and Lock?
cachedValues, exists := nh.cache[getCacheKey(cache, shardID)]
if exists {
return cachedValues
}
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.
sure. good idea. applied the double-checked locking pattern
observer/holder/nodesHolder_test.go
Outdated
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNodesHolder_ConstructorAndGetters(t *testing.T) { |
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.
t.Parallel() on all tests?
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.
right. added
observer/holder/nodesHolder_test.go
Outdated
func TestNodesHolder_ConstructorAndGetters(t *testing.T) { | ||
nh, err := NewNodesHolder([]*data.NodeData{}, []*data.NodeData{}, data.AvailabilityAll) | ||
require.Equal(t, errEmptyNodesList, err) | ||
require.True(t, check.IfNil(nh)) |
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.
require.True(t, check.IfNil(nh)) | |
require.Nil(t, nh) |
+new test for IsInterfaceNil
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.
done
observer/holder/nodesHolder_test.go
Outdated
|
||
nh, err = NewNodesHolder(syncedNodes, fallbackNodes, data.AvailabilityAll) | ||
require.NoError(t, err) | ||
require.False(t, nh.IsInterfaceNil()) |
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.
require.False(t, nh.IsInterfaceNil()) | |
require.NotNil(t, nh) |
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.
done
observer/holder/nodesHolder_test.go
Outdated
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNodesHolder_ConstructorAndGetters(t *testing.T) { |
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.
perhaps add some calls to Count() here for 100% coverage
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.
It has been called on the concurrency test, but added here as well
@@ -35,11 +35,11 @@ func NewCircularQueueNodesProvider(observers []*data.NodeData, configurationFile | |||
} | |||
|
|||
// GetNodesByShardId will return a slice of observers for the given shard | |||
func (cqnp *circularQueueNodesProvider) GetNodesByShardId(shardId uint32) ([]*data.NodeData, error) { | |||
func (cqnp *circularQueueNodesProvider) GetNodesByShardId(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { | |||
cqnp.mutNodes.Lock() |
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.
RLock ?
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.
since I also perform write operations, I would leave it as it is
@@ -51,11 +51,11 @@ func (cqnp *circularQueueNodesProvider) GetNodesByShardId(shardId uint32) ([]*da | |||
} | |||
|
|||
// GetAllNodes will return a slice containing all observers | |||
func (cqnp *circularQueueNodesProvider) GetAllNodes() ([]*data.NodeData, error) { | |||
func (cqnp *circularQueueNodesProvider) GetAllNodes(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { | |||
cqnp.mutNodes.Lock() |
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.
RLock ?
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.
since I also perform write operations, I would leave it as it is
@@ -111,6 +111,15 @@ type AccountQueryOptions struct { | |||
HintEpoch core.OptionalUint32 | |||
} | |||
|
|||
// AreHistoricalCoordinatesSet returns true if historical block coordinates are set | |||
func (a AccountQueryOptions) AreHistoricalCoordinatesSet() bool { |
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.
unit test?
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.
added
observer/baseNodeProvider.go
Outdated
if hasBackup { | ||
return []*data.NodeData{backupNode}, nil | ||
} | ||
|
||
return nil, ErrShardNotAvailable | ||
} | ||
|
||
func (bnp *baseNodeProvider) getSyncedNodesUnprotected() ([]*data.NodeData, error) { | ||
func filterNodesInShard(nodes []*data.NodeData, shardID uint32) []*data.NodeData { |
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.
I guess we do not need this if the bnp.regularNodes.GetXXXNodes() methods will return the nodes as maps: map[uint32][]*data.NodeData, where the key uint32 is the shard ID
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.
refactored
observer/holder/nodesHolder.go
Outdated
} | ||
|
||
func copyNodes(nodes []*data.NodeData) []*data.NodeData { | ||
sliceCopy := make([]*data.NodeData, 0, len(nodes)) |
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.
could have used copy
method. However, if we are going to refactor this implementation to hold the nodes as maps, we will still require the iteration on the keys of the map. We can then use copy
for the slices
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.
refactored
log = logger.GetOrCreate("observer/holder") | ||
) | ||
|
||
type nodesHolder struct { |
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.
as discussed, we can greatly simplify this struct
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.
refactored
observer/holder/nodesHolder.go
Outdated
nh.allNodes = make(map[uint32][]*data.NodeData) | ||
nh.cache = make(map[string][]*data.NodeData) | ||
for _, node := range nodesWithSyncStatus { | ||
shouldSkipNode := nh.availability == data.AvailabilityRecent && !node.IsSnapshotless || |
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.
please split this in 2. You have 2 &&
operators along with one ||
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.
done
mapToReturn := make(map[uint32][]*data.NodeData) | ||
// in the first step, consider all the nodes to be active | ||
for _, node := range regularNodes { | ||
node.IsSynced = true |
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.
is this ok? You change a property of the provided slice of nodes
Why not use a clone method?
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.
right. created a slice clone function and used it
observer/holder/nodesHolder_test.go
Outdated
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNodesHolder_ConstructorAndGetters(t *testing.T) { |
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.
Let's stop reinventing new ways of writing tests, especially for complex components. So, let's have a proper test for the constructor and for all setters & getters
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.
added a new test for the constructor. For getters, I preferred to keep the same test (split in 2: before and after update) because one getter's response is considered 'correct' if the other getter's response is also correct
observer/holder/nodesHolder_test.go
Outdated
|
||
require.Equal(t, []*data.NodeData{}, nh.GetOutOfSyncFallbackNodes(0)) | ||
require.Equal(t, []*data.NodeData{fallbackNodes[1]}, nh.GetOutOfSyncFallbackNodes(1)) | ||
require.Equal(t, []*data.NodeData{}, nh.GetOutOfSyncFallbackNodes(core.MetachainShardId)) |
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.
so much for "a test should test one thing"
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.
refactored
@@ -507,10 +299,18 @@ func initAllNodesSlice(nodesOnShards map[uint32][]*data.NodeData) ([]*data.NodeD | |||
} | |||
|
|||
node := nodesOnShards[shardID][i] | |||
if node.IsFallback { | |||
fallbackNodes = append(fallbackNodes, node) | |||
if node.IsSnapshotless { |
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.
pretty ugly if & else
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.
couldn't find a way to refactor it
process/accountProcessor.go
Outdated
@@ -452,3 +463,11 @@ func (ap *AccountProcessor) IsDataTrieMigrated(address string, options common.Ac | |||
|
|||
return nil, ErrSendingRequest | |||
} | |||
|
|||
func getAvailabilityBasedOnAccountQueryOptions(options common.AccountQueryOptions) data.ObserverDataAvailabilityType { |
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.
I would create a stateless component for these If's because the ifs are higher level decision-making code
The component could also have functions that will be used in nodesHolder component L58
L157
L182
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.
done
observer/holder/nodesHolder.go
Outdated
func cloneNodesSlice(input []*data.NodeData) []*data.NodeData { | ||
clonedSlice := make([]*data.NodeData, len(input)) | ||
for idx, node := range input { | ||
clonedSlice[idx] = &data.NodeData{ |
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.
this works but it is not future changes proof
Could have been:
clonedNodeData := *node
clonedSlice[idx] = &clonedNodeData
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.
right. done
fcd67a4
The base branch was changed.
8f0c5d9
into
feat/snapshotless-observer-support
Added support for snapshotless observers. When configuring snapshotless observers, they will be used for real-time requests (if available).
Example:
/address/erd1..
will call snapshotless observers, being a real-time request/hyperblock/by-nonce/..
will call regular observers, being a historical requestif no snapshotless observer is active, the requests that were meant for them will be sent to the regular observers