Skip to content

Commit

Permalink
fix: unittest
Browse files Browse the repository at this point in the history
  • Loading branch information
joanestebanr committed Nov 11, 2024
1 parent aaf9254 commit 8b382e3
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 59 deletions.
120 changes: 62 additions & 58 deletions aggsender/block_notifier_polling.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ func (b *BlockNotifierPolling) String() string {
return res
}

// StartAsync starts the BlockNotifierPolling in a new goroutine
func (b *BlockNotifierPolling) StartAsync(ctx context.Context) {
go b.Start(ctx)
}

// Start starts the BlockNotifierPolling blocking the current goroutine
func (b *BlockNotifierPolling) Start(ctx context.Context) {
ticker := time.NewTimer(b.config.CheckNewBlockInterval)
Expand All @@ -97,9 +92,12 @@ func (b *BlockNotifierPolling) Start(ctx context.Context) {
case <-ctx.Done():
return
case <-ticker.C:
delay, newStatus := b.step(ctx, status)
delay, newStatus, event := b.step(ctx, status)
status = newStatus
b.setGlobalStatus(status)
if event != nil {
b.Publish(*event)
}
ticker.Reset(delay)
}
}
Expand All @@ -121,67 +119,70 @@ func (b *BlockNotifierPolling) getGlobalStatus() *blockNotifierPollingInternalSt
return &copyStatus
}

// step is the main function of the BlockNotifierPolling, it checks if there is a new block
// it returns:
// - the delay for the next check
// - the new status
// - the new even to emit or nil
func (b *BlockNotifierPolling) step(ctx context.Context,
status *blockNotifierPollingInternalStatus) (time.Duration, *blockNotifierPollingInternalStatus) {
previousState *blockNotifierPollingInternalStatus) (time.Duration,
*blockNotifierPollingInternalStatus, *types.EventNewBlock) {
currentBlock, err := b.ethClient.HeaderByNumber(ctx, b.blockFinality)
if err != nil || currentBlock == nil {
if err == nil && currentBlock == nil {
err = fmt.Errorf("failed to get block number: return a nil block")
}
if err != nil {
b.logger.Errorf("Failed to get block number: %v", err)
return b.nextBlockRequestDelay(status, err), status.clear()
}
if status == nil {
status = status.intialBlock(currentBlock.Number.Uint64())
return b.nextBlockRequestDelay(status, nil), status
}

if currentBlock.Number.Uint64() != status.lastBlockSeen {
b.Publish(types.EventNewBlock{
BlockNumber: currentBlock.Number.Uint64(),
BlockFinalityType: b.config.BlockFinalityType,
})
now := timeNowFunc()
timePreviousBlock := now.Sub(status.lastBlockTime)
status.previousBlockTime = &timePreviousBlock
status.lastBlockTime = now

if currentBlock.Number.Uint64()-status.lastBlockSeen != 1 {
b.logger.Warnf("Missed block(s) [finality:%s]: %d -> %d",
b.config.BlockFinalityType, status.lastBlockSeen, currentBlock.Number.Uint64())
status.previousBlockTime = nil
status.lastBlockSeen = currentBlock.Number.Uint64()
return b.nextBlockRequestDelay(status, nil), status
}

status.lastBlockSeen = currentBlock.Number.Uint64()
newState := previousState.clear()
return b.nextBlockRequestDelay(nil, err), newState, nil
}
if previousState == nil {
newState := previousState.intialBlock(currentBlock.Number.Uint64())
return b.nextBlockRequestDelay(previousState, nil), newState, nil
}
if currentBlock.Number.Uint64() == previousState.lastBlockSeen {
// No new block, so no changes on state
return b.nextBlockRequestDelay(previousState, nil), previousState, nil
}
// New blockNumber!
eventToEmit := &types.EventNewBlock{
BlockNumber: currentBlock.Number.Uint64(),
BlockFinalityType: b.config.BlockFinalityType,
}

b.logger.Debugf("New block seen [finality:%s]: %d. blockRate:%s",
b.config.BlockFinalityType, currentBlock.Number.Uint64(), status.previousBlockTime)
if currentBlock.Number.Uint64()-previousState.lastBlockSeen != 1 {
b.logger.Warnf("Missed block(s) [finality:%s]: %d -> %d",
b.config.BlockFinalityType, previousState.lastBlockSeen, currentBlock.Number.Uint64())
// It start from scratch because something fails in calculation of block period
newState := previousState.clear()
return b.nextBlockRequestDelay(nil, nil), newState, eventToEmit
}
return b.nextBlockRequestDelay(status, nil), status
newState := previousState.incommingNewBlock(currentBlock.Number.Uint64())
b.logger.Debugf("New block seen [finality:%s]: %d. blockRate:%s",
b.config.BlockFinalityType, currentBlock.Number.Uint64(), newState.previousBlockTime)

return b.nextBlockRequestDelay(newState, nil), newState, eventToEmit
}

func (b *BlockNotifierPolling) nextBlockRequestDelay(status *blockNotifierPollingInternalStatus,
err error) time.Duration {
if b.config.CheckNewBlockInterval == AutomaticBlockInterval {
if status == nil {
return minBlockInterval
}
if status.previousBlockTime == nil {
// First interation is done with maximum precision
return minBlockInterval
}
if status.previousBlockTime != nil {
now := timeNowFunc()
expectedTimeNextBlock := status.lastBlockTime.Add(*status.previousBlockTime)
distanceToNextBlock := expectedTimeNextBlock.Sub(now)
interval := distanceToNextBlock * 4 / 5 //nolint:mnd // 80% of for reach the next block
return max(minBlockInterval, min(maxBlockInterval, interval))
}
}
if err == nil {
return b.config.CheckNewBlockInterval
}
// If error we wait twice the interval
return b.config.CheckNewBlockInterval * 2 //nolint:mnd // 2 times the interval
// Initial stages wait the minimum interval to increas accuracy
if status == nil || status.previousBlockTime == nil {
return minBlockInterval
}
if err != nil {
// If error we wait twice the min interval
return minBlockInterval * 2 //nolint:mnd // 2 times the interval
}
// we have a previous block time so we can calculate the interval
now := timeNowFunc()
expectedTimeNextBlock := status.lastBlockTime.Add(*status.previousBlockTime)
distanceToNextBlock := expectedTimeNextBlock.Sub(now)
interval := distanceToNextBlock * 4 / 5 //nolint:mnd // 80% of for reach the next block
return max(minBlockInterval, min(maxBlockInterval, interval))
}

type blockNotifierPollingInternalStatus struct {
Expand Down Expand Up @@ -209,9 +210,12 @@ func (s *blockNotifierPollingInternalStatus) intialBlock(block uint64) *blockNot
}
}

func (s *blockNotifierPollingInternalStatus) incommingBlock(block uint64) *blockNotifierPollingInternalStatus {
func (s *blockNotifierPollingInternalStatus) incommingNewBlock(block uint64) *blockNotifierPollingInternalStatus {
now := timeNowFunc()
timePreviousBlock := now.Sub(s.lastBlockTime)
return &blockNotifierPollingInternalStatus{
lastBlockSeen: block,
lastBlockTime: timeNowFunc(),
lastBlockSeen: block,
lastBlockTime: now,
previousBlockTime: &timePreviousBlock,
}
}
94 changes: 93 additions & 1 deletion aggsender/block_notifier_polling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/0xPolygon/cdk/aggsender/mocks"
aggsendertypes "github.com/0xPolygon/cdk/aggsender/types"
"github.com/0xPolygon/cdk/etherman"
"github.com/0xPolygon/cdk/log"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -29,7 +30,7 @@ func TestExploratoryBlockNotifierPolling(t *testing.T) {
BlockFinalityType: etherman.LatestBlock,
}, log.WithFields("test", "test"), nil)
require.NoError(t, errSut)
sut.StartAsync(context.Background())
go sut.Start(context.Background())
ch := sut.Subscribe("test")
for {
select {
Expand All @@ -38,6 +39,97 @@ func TestExploratoryBlockNotifierPolling(t *testing.T) {
}
}
}

func TestBlockNotifierPollingStep(t *testing.T) {
time0 := time.Unix(1731322117, 0)
period0 := time.Second * 10
period0_80percent := time.Second * 8
time1 := time0.Add(period0)
tests := []struct {
name string
previousStatus *blockNotifierPollingInternalStatus
HeaderByNumberError bool
HeaderByNumberErrorNumber uint64
forcedTime time.Time
expectedStatus *blockNotifierPollingInternalStatus
expectedDelay time.Duration
expectedEvent *aggsendertypes.EventNewBlock
}{
{
name: "initial->receive block",
previousStatus: nil,
HeaderByNumberError: false,
HeaderByNumberErrorNumber: 100,
forcedTime: time0,
expectedStatus: &blockNotifierPollingInternalStatus{
lastBlockSeen: 100,
lastBlockTime: time0,
},
expectedDelay: minBlockInterval,
expectedEvent: nil,
},
{
name: "received block->error",
previousStatus: nil,
HeaderByNumberError: true,
forcedTime: time0,
expectedStatus: &blockNotifierPollingInternalStatus{},
expectedDelay: minBlockInterval,
expectedEvent: nil,
},

{
name: "have block period->receive new block",
previousStatus: &blockNotifierPollingInternalStatus{
lastBlockSeen: 100,
lastBlockTime: time0,
previousBlockTime: &period0,
},
HeaderByNumberError: false,
HeaderByNumberErrorNumber: 101,
forcedTime: time1,
expectedStatus: &blockNotifierPollingInternalStatus{
lastBlockSeen: 101,
lastBlockTime: time1,
previousBlockTime: &period0,
},
expectedDelay: period0_80percent,
expectedEvent: &aggsendertypes.EventNewBlock{
BlockNumber: 101,
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
//t.Parallel()

Check failure on line 105 in aggsender/block_notifier_polling_test.go

View workflow job for this annotation

GitHub Actions / lint

commentFormatting: put a space between `//` and comment text (gocritic)
testData := newBlockNotifierPollingTestData(t, nil)

timeNowFunc = func() time.Time {
return tt.forcedTime
}

if tt.HeaderByNumberError == false {
hdr1 := &types.Header{
Number: big.NewInt(int64(tt.HeaderByNumberErrorNumber)),
}
testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(hdr1, nil).Once()
} else {
testData.ethClientMock.EXPECT().HeaderByNumber(mock.Anything, mock.Anything).Return(nil, fmt.Errorf("error")).Once()
}
delay, newStatus, event := testData.sut.step(context.TODO(), tt.previousStatus)
require.Equal(t, tt.expectedDelay, delay, "delay")
require.Equal(t, tt.expectedStatus, newStatus, "new_status")
if tt.expectedEvent == nil {
require.Nil(t, event, "send_event")
} else {
require.Equal(t, tt.expectedEvent.BlockNumber, event.BlockNumber, "send_event")
}
})
}

}

Check failure on line 131 in aggsender/block_notifier_polling_test.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)

func TestDelayNoPreviousBLock(t *testing.T) {
testData := newBlockNotifierPollingTestData(t, nil)
status := blockNotifierPollingInternalStatus{
Expand Down

0 comments on commit 8b382e3

Please # to comment.