Skip to content
Open
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
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
TimeStamp() time.Time
IsInterfaceNil() bool
}

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
16 changes: 16 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,9 @@ var log = logger.GetOrCreate("process/track")

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

// receivedProofDelay is the fraction of the full round time to accept a proof
Comment thread
sstanculeanu marked this conversation as resolved.
Outdated
const receivedProofDelay = 0.5

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

currentRoundStart := bbt.roundHandler.TimeStamp()
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(currentRoundStart, 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,
currentRoundStart.UnixMilli(),

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 new time-window check is effectively inert with the real consensus/round implementation: RemainingTime(TimeStamp(), 1.5*TimeDuration) will stay >0 because TimeStamp() is updated each round and elapsed is always < TimeDuration(). As a result, headers/proofs won’t actually be dropped “when received too late in the next round”. Consider basing the window on the received item’s round (e.g., compute the start timestamp of that round and enforce acceptance only for the first receivedProofDelay fraction of it), or otherwise use a start time that doesn’t reset every round when validating next-round items.

Suggested change
roundDuration := float64(bbt.roundHandler.TimeDuration())
maxTimeToAcceptProof := time.Duration(roundDuration + roundDuration*receivedProofDelay)
timeLeftToAcceptProof := bbt.roundHandler.RemainingTime(currentRoundStart, maxTimeToAcceptProof)
if timeLeftToAcceptProof <= 0 {
return fmt.Errorf("%w header round: %d, current round timestamp: %d, time left to accept proof: %d",
process.ErrHigherRoundInBlock,
round,
currentRoundStart.UnixMilli(),
roundDuration := bbt.roundHandler.TimeDuration()
maxTimeToAcceptProof := time.Duration(float64(roundDuration) * (1 + receivedProofDelay))
roundDiff := int64(round) - int64(bbt.roundHandler.Index())
itemRoundStart := currentRoundStart.Add(time.Duration(roundDiff) * roundDuration)
timeLeftToAcceptProof := bbt.roundHandler.RemainingTime(itemRoundStart, maxTimeToAcceptProof)
if timeLeftToAcceptProof <= 0 {
return fmt.Errorf("%w header round: %d, item round timestamp: %d, time left to accept proof: %d",
process.ErrHigherRoundInBlock,
round,
itemRoundStart.UnixMilli(),

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.

fixed

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