Skip to content
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

Update check tip util functions to return both at-tip status and block id #294

Merged
merged 3 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,10 @@ type BlockStorageHelper interface {
// to this interface.
}

// CheckNetworkTip returns block identifier if the block returned by network/status
// endpoint is at tip. Note that the tipDelay param takes tip delay in seconds.
// CheckNetworkTip returns a boolean indicating if the block returned by
// network/status is at tip. It also returns the block identifier
// returned by network/status.
// Note that the tipDelay param takes tip delay in seconds.
// Block returned by network/status is considered to be at tip if one of the
// following two conditions is met:
// (1) the block was produced within tipDelay of current time
Expand All @@ -208,64 +210,70 @@ func CheckNetworkTip(ctx context.Context,
network *types.NetworkIdentifier,
tipDelay int64,
f FetcherHelper,
) (*types.BlockIdentifier, error) {
) (bool, *types.BlockIdentifier, error) {
// todo: refactor CheckNetworkTip and its usages to accept metadata and pass it to
// NetworkStatusRetry call.
status, fetchErr := f.NetworkStatusRetry(ctx, network, nil)
if fetchErr != nil {
return nil, fmt.Errorf("%w: unable to fetch network status", fetchErr.Err)
return false, nil, fmt.Errorf("%w: unable to fetch network status", fetchErr.Err)
}

// if the block timestamp is within tip delay of current time,
// it can be considered to be at tip.
if AtTip(tipDelay, status.CurrentBlockTimestamp) {
return status.CurrentBlockIdentifier, nil
return true, status.CurrentBlockIdentifier, nil
}

// If the sync status returned by network/status is true, we should consider the block to be at
// tip.
if status.SyncStatus != nil && status.SyncStatus.Synced != nil && *status.SyncStatus.Synced {
return status.CurrentBlockIdentifier, nil
return true, status.CurrentBlockIdentifier, nil
}

return nil, nil
return false, status.CurrentBlockIdentifier, nil
}

// CheckStorageTip returns block identifier if the current block
// in storage is at tip. Note that the tipDelay param takes tip delay in seconds.
// A block in storage is considered to be at tip if one of the following two conditions is met
// CheckStorageTip returns a boolean indicating if the current
// block returned by block storage helper is at tip. It also
// returns the block identifier of the current storage block.
// Note that the tipDelay param takes tip delay in seconds.
// A block in storage is considered to be at tip if one of the
// following two conditions is met:
// (1) the block was produced within tipDelay of current time
// (i.e. block timestamp >= current time - tipDelay)
// (2) CheckNetworkTip returns the same block as the current block in storage
// (2) CheckNetworkTip returns true and the block it returns
// is same as the current block in storage
func CheckStorageTip(ctx context.Context,
network *types.NetworkIdentifier,
tipDelay int64,
f FetcherHelper,
b BlockStorageHelper,
) (*types.BlockIdentifier, error) {
) (bool, *types.BlockIdentifier, error) {
blockResponse, err := b.GetBlockLazy(ctx, nil)
if errors.Is(err, storageErrors.ErrHeadBlockNotFound) {
// If no blocks exist in storage yet, we are not at tip
return nil, nil
return false, nil, nil
}

currentStorageBlock := blockResponse.Block
if AtTip(tipDelay, currentStorageBlock.Timestamp) {
return currentStorageBlock.BlockIdentifier, nil
return true, currentStorageBlock.BlockIdentifier, nil
}

// if latest block in storage is not at tip,
// check network status
tipBlock, fetchErr := CheckNetworkTip(ctx, network, tipDelay, f)
networkAtTip, tipBlock, fetchErr := CheckNetworkTip(ctx, network, tipDelay, f)
if fetchErr != nil {
return nil, fmt.Errorf("%w: unable to fetch network status", fetchErr)
return false,
currentStorageBlock.BlockIdentifier,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: return nil here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was on the fence about this. We could leave it to the caller to use or ignore the identifier based on the error.
But it will be more consistent to return nil in case of errors. Will change.

fmt.Errorf("%w: unable to fetch network status", fetchErr)
}

if types.Hash(tipBlock) == types.Hash(currentStorageBlock.BlockIdentifier) {
return currentStorageBlock.BlockIdentifier, nil
if networkAtTip && types.Hash(tipBlock) == types.Hash(currentStorageBlock.BlockIdentifier) {
return true, currentStorageBlock.BlockIdentifier, nil
}

return nil, nil
return false, currentStorageBlock.BlockIdentifier, nil
}

// CheckNetworkSupported checks if a Rosetta implementation supports a given
Expand Down
98 changes: 55 additions & 43 deletions utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,9 @@ func TestCheckNetworkTip(t *testing.T) {
helper *mocks.FetcherHelper
tipDelay int64

expectedResult *types.BlockIdentifier
expectedError error
expectedAtTip bool
expectedIdentifier *types.BlockIdentifier
expectedError error
}{
"at tip": {
helper: func() *mocks.FetcherHelper {
Expand All @@ -412,9 +413,10 @@ func TestCheckNetworkTip(t *testing.T) {

return mockHelper
}(),
tipDelay: 100,
expectedResult: blockIdentifier,
expectedError: nil,
tipDelay: 100,
expectedAtTip: true,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"not at tip": {
helper: func() *mocks.FetcherHelper {
Expand All @@ -435,9 +437,10 @@ func TestCheckNetworkTip(t *testing.T) {

return mockHelper
}(),
tipDelay: 100,
expectedResult: nil,
expectedError: nil,
tipDelay: 100,
expectedAtTip: false,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"synced tip": {
helper: func() *mocks.FetcherHelper {
Expand All @@ -461,9 +464,10 @@ func TestCheckNetworkTip(t *testing.T) {

return mockHelper
}(),
tipDelay: 100,
expectedResult: blockIdentifier,
expectedError: nil,
tipDelay: 100,
expectedAtTip: true,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"not synced tip": {
helper: func() *mocks.FetcherHelper {
Expand All @@ -487,9 +491,10 @@ func TestCheckNetworkTip(t *testing.T) {

return mockHelper
}(),
tipDelay: 100,
expectedResult: nil,
expectedError: nil,
tipDelay: 100,
expectedAtTip: false,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"error": {
helper: func() *mocks.FetcherHelper {
Expand All @@ -510,18 +515,21 @@ func TestCheckNetworkTip(t *testing.T) {
return mockHelper
}(),
tipDelay: 100,
expectedAtTip: false,
expectedError: fetcher.ErrRequestFailed,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
tipBlock, err := CheckNetworkTip(ctx, network, test.tipDelay, test.helper)
atTip, tipBlock, err := CheckNetworkTip(ctx, network, test.tipDelay, test.helper)
if test.expectedError != nil {
assert.Nil(t, tipBlock)
assert.False(t, atTip)
assert.Equal(t, test.expectedIdentifier, tipBlock)
assert.True(t, errors.Is(err, test.expectedError))
} else {
assert.Equal(t, test.expectedResult, tipBlock)
assert.Equal(t, test.expectedAtTip, atTip)
assert.Equal(t, test.expectedIdentifier, tipBlock)
assert.NoError(t, err)
}
test.helper.AssertExpectations(t)
Expand All @@ -537,8 +545,9 @@ func TestCheckStorageTip(t *testing.T) {
b *mocks.BlockStorageHelper
tipDelay int64

expectedResult *types.BlockIdentifier
expectedError error
expectedAtTip bool
expectedIdentifier *types.BlockIdentifier
expectedError error
}{
"stored block within tip delay": {
f: &mocks.FetcherHelper{},
Expand All @@ -559,9 +568,10 @@ func TestCheckStorageTip(t *testing.T) {
).Once()
return mockHelper
}(),
tipDelay: 1000,
expectedResult: blockIdentifier,
expectedError: nil,
tipDelay: 1000,
expectedAtTip: true,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"stored block not within tip delay, network synced ": {
f: func() *mocks.FetcherHelper {
Expand Down Expand Up @@ -595,11 +605,12 @@ func TestCheckStorageTip(t *testing.T) {
).Once()
return mockHelper
}(),
tipDelay: 100,
expectedResult: blockIdentifier,
expectedError: nil,
tipDelay: 100,
expectedAtTip: true,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"stored block not within tip delay, network not synced ": {
"stored block not within tip delay, network not synced": {
f: func() *mocks.FetcherHelper {
mockHelper := &mocks.FetcherHelper{}
mockHelper.On("NetworkStatusRetry",
Expand Down Expand Up @@ -631,9 +642,10 @@ func TestCheckStorageTip(t *testing.T) {
).Once()
return mockHelper
}(),
tipDelay: 100,
expectedResult: nil,
expectedError: nil,
tipDelay: 100,
expectedAtTip: false,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"stored block and network blocks different": {
f: func() *mocks.FetcherHelper {
Expand Down Expand Up @@ -662,20 +674,18 @@ func TestCheckStorageTip(t *testing.T) {
).Return(
&types.BlockResponse{
Block: &types.Block{
BlockIdentifier: &types.BlockIdentifier{
Hash: "block",
Index: 1,
},
Timestamp: Milliseconds() - 300*MillisecondsInSecond,
BlockIdentifier: blockIdentifier,
Timestamp: Milliseconds() - 300*MillisecondsInSecond,
},
},
nil,
).Once()
return mockHelper
}(),
tipDelay: 100,
expectedResult: nil,
expectedError: nil,
tipDelay: 100,
expectedAtTip: false,
expectedIdentifier: blockIdentifier,
expectedError: nil,
},
"no blocks in storage": {
f: &mocks.FetcherHelper{},
Expand All @@ -691,20 +701,22 @@ func TestCheckStorageTip(t *testing.T) {
).Once()
return mockHelper
}(),
tipDelay: 100,
expectedResult: nil,
expectedError: nil,
tipDelay: 100,
expectedAtTip: false,
expectedIdentifier: nil,
expectedError: nil,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
tipBlock, err := CheckStorageTip(ctx, network, test.tipDelay, test.f, test.b)
atTip, tipBlock, err := CheckStorageTip(ctx, network, test.tipDelay, test.f, test.b)
if test.expectedError != nil {
assert.Nil(t, tipBlock)
assert.False(t, atTip)
assert.Equal(t, test.expectedAtTip, atTip)
assert.True(t, errors.Is(err, test.expectedError))
} else {
assert.Equal(t, test.expectedResult, tipBlock)
assert.Equal(t, test.expectedIdentifier, tipBlock)
assert.NoError(t, err)
}
test.f.AssertExpectations(t)
Expand Down