Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
20 changes: 15 additions & 5 deletions epochStart/mock/rounderStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
type RoundHandlerStub struct {
RoundIndex int64

IndexCalled func() int64
TimeDurationCalled func() time.Duration
TimeStampCalled func() time.Time
UpdateRoundCalled func(time.Time, time.Time)
RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration
IndexCalled func() int64
TimeDurationCalled func() time.Duration
TimeStampCalled func() time.Time
UpdateRoundCalled func(time.Time, time.Time)
RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration
GetTimeStampForRoundCalled func(round uint64) uint64
}

// Index -
Expand Down Expand Up @@ -61,6 +62,15 @@ func (rndm *RoundHandlerStub) RemainingTime(startTime time.Time, maxTime time.Du
return 4000 * time.Millisecond
}

// GetTimeStampForRound -
func (rndm *RoundHandlerStub) GetTimeStampForRound(round uint64) uint64 {
if rndm.GetTimeStampForRoundCalled != nil {
return rndm.GetTimeStampForRoundCalled(round)
}

return uint64(time.Unix(0, 0).UnixMilli())
}

// IsInterfaceNil returns true if there is no value under the interface
func (rndm *RoundHandlerStub) IsInterfaceNil() bool {
return rndm == nil
Expand Down
5 changes: 4 additions & 1 deletion integrationTests/testProcessorNode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3540,7 +3540,10 @@ func (tpn *TestProcessorNode) MiniBlocksPresent(hashes [][]byte) bool {
}

func (tpn *TestProcessorNode) initRoundHandler(roundTime time.Duration) {
tpn.RoundHandler = &mock.RoundHandlerMock{TimeDurationField: roundTime}
tpn.RoundHandler = &mock.RoundHandlerMock{
TimeDurationField: roundTime,
RemainingTimeField: roundTime,
}
}

func (tpn *TestProcessorNode) initRequestedItemsHandler() {
Expand Down
2 changes: 2 additions & 0 deletions process/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,8 @@ type RoundTimeDurationHandler interface {
type RoundHandler interface {
Index() int64
TimeDuration() time.Duration
RemainingTime(startTime time.Time, maxTime time.Duration) time.Duration
GetTimeStampForRound(round uint64) uint64
IsInterfaceNil() bool
}

Expand Down
20 changes: 15 additions & 5 deletions process/mock/roundStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (

// RoundStub -
type RoundStub struct {
IndexCalled func() int64
TimeDurationCalled func() time.Duration
TimeStampCalled func() time.Time
UpdateRoundCalled func(time.Time, time.Time)
RemainingTimeCalled func(time.Time, time.Duration) time.Duration
IndexCalled func() int64
TimeDurationCalled func() time.Duration
TimeStampCalled func() time.Time
UpdateRoundCalled func(time.Time, time.Time)
RemainingTimeCalled func(time.Time, time.Duration) time.Duration
GetTimeStampForRoundCalled func(round uint64) uint64
}

// Index -
Expand Down Expand Up @@ -38,6 +39,15 @@ func (rnds *RoundStub) RemainingTime(startTime time.Time, maxTime time.Duration)
return rnds.RemainingTimeCalled(startTime, maxTime)
}

// GetTimeStampForRound -
func (rnds *RoundStub) GetTimeStampForRound(round uint64) uint64 {
if rnds.GetTimeStampForRoundCalled != nil {
return rnds.GetTimeStampForRoundCalled(round)
}

return uint64(time.Unix(0, 0).UnixMilli())
}

// IsInterfaceNil --
func (rnds *RoundStub) IsInterfaceNil() bool {
return rnds == nil
Expand Down
6 changes: 5 additions & 1 deletion process/mock/rounderMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type RoundHandlerMock struct {
RoundTimeStamp time.Time
RoundTimeDuration time.Duration
BeforeGenesisCalled func() bool
RemainingTimeCalled func(startTime time.Time, maxTime time.Duration) time.Duration
}

// BeforeGenesis -
Expand Down Expand Up @@ -55,7 +56,10 @@ func (rndm *RoundHandlerMock) UpdateRound(genesisRoundTimeStamp time.Time, timeS
}

// RemainingTime -
func (rndm *RoundHandlerMock) RemainingTime(_ time.Time, _ time.Duration) time.Duration {
func (rndm *RoundHandlerMock) RemainingTime(startTime time.Time, maxTime time.Duration) time.Duration {
if rndm.RemainingTimeCalled != nil {
return rndm.RemainingTimeCalled(startTime, maxTime)
}
return rndm.RoundTimeDuration
}

Expand Down
17 changes: 17 additions & 0 deletions process/track/baseBlockTrack.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sort"
"sync"
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
Expand All @@ -25,6 +26,10 @@ var log = logger.GetOrCreate("process/track")

const maxNonceDifference = 3 // TODO move this to a config file

// receivedProofDelay is the extra fraction of one full round time during which
// a proof is still accepted, i.e. total allowed time = roundDuration * (1 + receivedProofDelay)
const receivedProofDelay = 0.5

// HeaderInfo holds the information about a header
type HeaderInfo struct {
Hash []byte
Expand Down Expand Up @@ -494,6 +499,18 @@ func (bbt *baseBlockTrack) checkAgainstRoundHandler(round uint64) error {
nextRound)
}

roundTimestamp := time.UnixMilli(int64(bbt.roundHandler.GetTimeStampForRound(round)))
roundDuration := float64(bbt.roundHandler.TimeDuration())
maxTimeToAcceptProof := time.Duration(roundDuration + roundDuration*receivedProofDelay)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
maxTimeToAcceptProof := time.Duration(roundDuration + roundDuration*receivedProofDelay)
maxTimeToAcceptProof := time.Duration(currentRoundStart + roundDuration*receivedProofDelay)

it should be based on start round time, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

no, max time parameter is the total amount of time (900 ms in this case), as RemainingTime is called with currentRoundStart

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

aa, right

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

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

maxTimeToAcceptProof is built via float64(time.Duration) arithmetic. Converting durations to float can introduce precision/rounding issues and is harder to reason about. Prefer pure time.Duration math (e.g., derive the extra window using integer arithmetic or a rational numerator/denominator) so the acceptance window is exact and overflow-safe.

Suggested change
roundDuration := float64(bbt.roundHandler.TimeDuration())
maxTimeToAcceptProof := time.Duration(roundDuration + roundDuration*receivedProofDelay)
roundDuration := bbt.roundHandler.TimeDuration()
extraWindow := time.Duration(float64(roundDuration.Nanoseconds()) * receivedProofDelay)
maxTimeToAcceptProof := roundDuration + extraWindow

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

should be ok

timeLeftToAcceptProof := bbt.roundHandler.RemainingTime(roundTimestamp, maxTimeToAcceptProof)
if timeLeftToAcceptProof <= 0 {
return fmt.Errorf("%w header round: %d, current round timestamp: %d, time left to accept proof: %d",
process.ErrHigherRoundInBlock,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe use another error? this one might be misleading.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

updated

round,
roundTimestamp.UnixMilli(),
timeLeftToAcceptProof.Milliseconds())
}

return nil
}

Expand Down
38 changes: 37 additions & 1 deletion process/track/baseBlockTrack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sync"
"testing"
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
Expand Down Expand Up @@ -2371,7 +2372,9 @@ func TestBaseBlockTrack_CheckBlockAgainstRoundHandlerShouldWork(t *testing.T) {
currentRound := int64(50)
bbt.SetRoundHandler(
&mock.RoundHandlerMock{
RoundIndex: currentRound,
RoundIndex: currentRound,
RoundTimeStamp: time.Now(),
RoundTimeDuration: time.Second,
},
)

Expand All @@ -2383,6 +2386,39 @@ func TestBaseBlockTrack_CheckBlockAgainstRoundHandlerShouldWork(t *testing.T) {
assert.Nil(t, err)
}

func TestBaseBlockTrack_CheckBlockAgainstRoundHandlerShouldFailOnInvalidWindow(t *testing.T) {
t.Parallel()

bbt := track.NewBaseBlockTrack()
currentRound := int64(50)
roundDuration := time.Millisecond * 200
bbt.SetRoundHandler(
&mock.RoundHandlerMock{
RoundIndex: currentRound,
RoundTimeStamp: time.Now(),
RoundTimeDuration: roundDuration,
RemainingTimeCalled: func(startTime time.Time, maxTime time.Duration) time.Duration {
currentTime := time.Now()
elapsedTime := currentTime.Sub(startTime)
remainingTime := maxTime - elapsedTime

return remainingTime
},
Comment on lines +2403 to +2409

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we use real RemainingTime method from round handler?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe use real round handler, and call UpdateRound several times to set the desired index (with lower test values)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

that should work too, tried to keep it simple

},
)

hdr := &block.Header{
Round: uint64(currentRound + 1), // proper round but received too late
}

// wait until after half of the next round passed
timeToSleep := roundDuration + time.Duration(float64(roundDuration)*0.6)
time.Sleep(timeToSleep)
Comment on lines +2420 to +2422

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

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

The test relies on real time (time.Sleep) to cross the acceptance window, which makes the suite slower and can be flaky under CI scheduling variance. Prefer a deterministic approach (e.g., set RoundTimeStamp far enough in the past or have RemainingTimeCalled return a controlled negative value) so the test doesn’t depend on wall-clock timing.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

should work

err := bbt.CheckBlockAgainstRoundHandler(hdr)
require.ErrorIs(t, err, process.ErrHigherRoundInBlock)
require.Contains(t, err.Error(), "current round timestamp")
}

// ------- CheckBlockAgainstFinal

func TestBaseBlockTrack_CheckBlockAgainstFinalNilHeaderShouldErr(t *testing.T) {
Expand Down
Loading