diff --git a/vms/proposervm/block.go b/vms/proposervm/block.go index 72628a2b4b00..d38e2e88d030 100644 --- a/vms/proposervm/block.go +++ b/vms/proposervm/block.go @@ -33,7 +33,6 @@ var ( errPChainHeightNotMonotonic = errors.New("non monotonically increasing P-chain height") errPChainHeightNotReached = errors.New("block P-chain height larger than current P-chain height") errTimeTooAdvanced = errors.New("time is too far advanced") - errProposerWindowNotStarted = errors.New("proposer window hasn't started") errUnexpectedProposer = errors.New("unexpected proposer for current window") errProposerMismatch = errors.New("proposer mismatch") errProposersNotActivated = errors.New("proposers haven't been activated yet") @@ -140,12 +139,7 @@ func (p *postForkCommonComponents) Verify( ) } - var shouldHaveProposer bool - if p.vm.Upgrades.IsDurangoActivated(parentTimestamp) { - shouldHaveProposer, err = p.verifyPostDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child) - } else { - shouldHaveProposer, err = p.verifyPreDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child) - } + shouldHaveProposer, err := p.verifyBlockDelay(ctx, parentTimestamp, parentPChainHeight, child) if err != nil { return err } @@ -203,24 +197,13 @@ func (p *postForkCommonComponents) buildChild( return nil, err } - var shouldBuildSignedBlock bool - if p.vm.Upgrades.IsDurangoActivated(parentTimestamp) { - shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPostDurango( - ctx, - parentID, - parentTimestamp, - parentPChainHeight, - newTimestamp, - ) - } else { - shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPreDurango( - ctx, - parentID, - parentTimestamp, - parentPChainHeight, - newTimestamp, - ) - } + shouldBuildSignedBlock, err := p.shouldBuildSignedBlock( + ctx, + parentID, + parentTimestamp, + parentPChainHeight, + newTimestamp, + ) if err != nil { return nil, err } @@ -332,42 +315,7 @@ func verifyIsNotOracleBlock(ctx context.Context, b snowman.Block) error { } } -func (p *postForkCommonComponents) verifyPreDurangoBlockDelay( - ctx context.Context, - parentTimestamp time.Time, - parentPChainHeight uint64, - blk *postForkBlock, -) (bool, error) { - var ( - blkTimestamp = blk.Timestamp() - childHeight = blk.Height() - proposerID = blk.Proposer() - ) - minDelay, err := p.vm.Windower.Delay( - ctx, - childHeight, - parentPChainHeight, - proposerID, - proposer.MaxVerifyWindows, - ) - if err != nil { - p.vm.ctx.Log.Error("unexpected block verification failure", - zap.String("reason", "failed to calculate required timestamp delay"), - zap.Stringer("blkID", blk.ID()), - zap.Error(err), - ) - return false, err - } - - delay := blkTimestamp.Sub(parentTimestamp) - if delay < minDelay { - return false, fmt.Errorf("%w: delay %s < minDelay %s", errProposerWindowNotStarted, delay, minDelay) - } - - return delay < proposer.MaxVerifyDelay, nil -} - -func (p *postForkCommonComponents) verifyPostDurangoBlockDelay( +func (p *postForkCommonComponents) verifyBlockDelay( ctx context.Context, parentTimestamp time.Time, parentPChainHeight uint64, @@ -406,7 +354,7 @@ func (p *postForkCommonComponents) verifyPostDurangoBlockDelay( } } -func (p *postForkCommonComponents) shouldBuildSignedBlockPostDurango( +func (p *postForkCommonComponents) shouldBuildSignedBlock( ctx context.Context, parentID ids.ID, parentTimestamp time.Time, @@ -450,7 +398,7 @@ func (p *postForkCommonComponents) shouldBuildSignedBlockPostDurango( // // TODO: After Durango activates, restructure this logic to separate // updating the scheduler from verifying the proposerID. - nextStartTime, err := p.vm.getPostDurangoSlotTime( + nextStartTime, err := p.vm.getSlotTime( ctx, parentHeight+1, parentPChainHeight, @@ -477,48 +425,3 @@ func (p *postForkCommonComponents) shouldBuildSignedBlockPostDurango( p.vm.notifyInnerBlockReady() return false, fmt.Errorf("%w: slot %d expects %s", errUnexpectedProposer, currentSlot, expectedProposerID) } - -func (p *postForkCommonComponents) shouldBuildSignedBlockPreDurango( - ctx context.Context, - parentID ids.ID, - parentTimestamp time.Time, - parentPChainHeight uint64, - newTimestamp time.Time, -) (bool, error) { - delay := newTimestamp.Sub(parentTimestamp) - if delay >= proposer.MaxBuildDelay { - return false, nil // time for any node to build an unsigned block - } - - parentHeight := p.innerBlk.Height() - proposerID := p.vm.ctx.NodeID - minDelay, err := p.vm.Windower.Delay(ctx, parentHeight+1, parentPChainHeight, proposerID, proposer.MaxBuildWindows) - if err != nil { - p.vm.ctx.Log.Error("unexpected build block failure", - zap.String("reason", "failed to calculate required timestamp delay"), - zap.Stringer("parentID", parentID), - zap.Error(err), - ) - return false, err - } - - if delay >= minDelay { - // it's time for this node to propose a block. It'll be signed or - // unsigned depending on the delay - return delay < proposer.MaxVerifyDelay, nil - } - - // It's not our turn to propose a block yet. This is likely caused by having - // previously notified the consensus engine to attempt to build a block on - // top of a block that is no longer the preferred block. - p.vm.ctx.Log.Debug("build block dropped", - zap.Time("parentTimestamp", parentTimestamp), - zap.Duration("minDelay", minDelay), - zap.Time("blockTimestamp", newTimestamp), - ) - - // In case the inner VM only issued one pendingTxs message, we should - // attempt to re-handle that once it is our turn to build the block. - p.vm.notifyInnerBlockReady() - return false, fmt.Errorf("%w: delay %s < minDelay %s", errProposerWindowNotStarted, delay, minDelay) -} diff --git a/vms/proposervm/block_test.go b/vms/proposervm/block_test.go index 91c8a9c23766..6623bebc0fd3 100644 --- a/vms/proposervm/block_test.go +++ b/vms/proposervm/block_test.go @@ -4,7 +4,6 @@ package proposervm import ( - "bytes" "context" "crypto/ecdsa" "crypto/elliptic" @@ -18,17 +17,14 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/consensus/snowman/snowmanmock" "github.com/ava-labs/avalanchego/snow/consensus/snowman/snowmantest" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/engine/snowman/block/blockmock" - "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/snow/validators/validatorsmock" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/proposervm/proposer" "github.com/ava-labs/avalanchego/vms/proposervm/proposer/proposermock" "github.com/ava-labs/avalanchego/vms/proposervm/scheduler/schedulermock" @@ -107,258 +103,12 @@ func TestPostForkCommonComponents_buildChild(t *testing.T) { require.Equal(builtBlk, gotChild.(*postForkBlock).innerBlk) } -func TestPreDurangoValidatorNodeBlockBuiltDelaysTests(t *testing.T) { - require := require.New(t) - ctx := context.Background() - - var ( - activationTime = time.Unix(0, 0) - durangoTime = mockable.MaxTime - ) - coreVM, valState, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) - defer func() { - require.NoError(proVM.Shutdown(ctx)) - }() - - // Build a post fork block. It'll be the parent block in our test cases - parentTime := time.Now().Truncate(time.Second) - proVM.Set(parentTime) - - coreParentBlk := snowmantest.BuildChild(snowmantest.Genesis) - coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return coreParentBlk, nil - } - coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { - switch blkID { - case coreParentBlk.ID(): - return coreParentBlk, nil - case snowmantest.GenesisID: - return snowmantest.Genesis, nil - default: - return nil, errUnknownBlock - } - } - coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { // needed when setting preference - switch { - case bytes.Equal(b, coreParentBlk.Bytes()): - return coreParentBlk, nil - case bytes.Equal(b, snowmantest.GenesisBytes): - return snowmantest.Genesis, nil - default: - return nil, errUnknownBlock - } - } - - parentBlk, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.NoError(parentBlk.Verify(ctx)) - require.NoError(parentBlk.Accept(ctx)) - - // Make sure preference is duly set - require.NoError(proVM.SetPreference(ctx, parentBlk.ID())) - require.Equal(proVM.preferred, parentBlk.ID()) - _, err = proVM.getPostForkBlock(ctx, parentBlk.ID()) - require.NoError(err) - - // Force this node to be the only validator, so to guarantee - // it'd be picked if block build time was before MaxVerifyDelay - valState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - // a validator with a weight large enough to fully fill the proposers list - weight := uint64(proposer.MaxBuildWindows * 2) - - return map[ids.NodeID]*validators.GetValidatorOutput{ - proVM.ctx.NodeID: { - NodeID: proVM.ctx.NodeID, - Weight: weight, - }, - }, nil - } - - coreChildBlk := snowmantest.BuildChild(coreParentBlk) - coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return coreChildBlk, nil - } - - { - // Set local clock before MaxVerifyDelay from parent timestamp. - // Check that child block is signed. - localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay - time.Second) - proVM.Set(localTime) - - childBlkIntf, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.IsType(&postForkBlock{}, childBlkIntf) - - childBlk := childBlkIntf.(*postForkBlock) - require.Equal(proVM.ctx.NodeID, childBlk.Proposer()) // signed block - } - - { - // Set local clock exactly MaxVerifyDelay from parent timestamp. - // Check that child block is unsigned. - localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay) - proVM.Set(localTime) - - childBlkIntf, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.IsType(&postForkBlock{}, childBlkIntf) - - childBlk := childBlkIntf.(*postForkBlock) - require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block - } - - { - // Set local clock between MaxVerifyDelay and MaxBuildDelay from parent - // timestamp. - // Check that child block is unsigned. - localTime := parentBlk.Timestamp().Add((proposer.MaxVerifyDelay + proposer.MaxBuildDelay) / 2) - proVM.Set(localTime) - - childBlkIntf, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.IsType(&postForkBlock{}, childBlkIntf) - - childBlk := childBlkIntf.(*postForkBlock) - require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block - } - - { - // Set local clock after MaxBuildDelay from parent timestamp. - // Check that child block is unsigned. - localTime := parentBlk.Timestamp().Add(proposer.MaxBuildDelay) - proVM.Set(localTime) - - childBlkIntf, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.IsType(&postForkBlock{}, childBlkIntf) - - childBlk := childBlkIntf.(*postForkBlock) - require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block - } -} - -func TestPreDurangoNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { - require := require.New(t) - ctx := context.Background() - - var ( - activationTime = time.Unix(0, 0) - durangoTime = mockable.MaxTime - ) - coreVM, valState, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) - defer func() { - require.NoError(proVM.Shutdown(ctx)) - }() - - // Build a post fork block. It'll be the parent block in our test cases - parentTime := time.Now().Truncate(time.Second) - proVM.Set(parentTime) - - coreParentBlk := snowmantest.BuildChild(snowmantest.Genesis) - coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return coreParentBlk, nil - } - coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { - switch blkID { - case coreParentBlk.ID(): - return coreParentBlk, nil - case snowmantest.GenesisID: - return snowmantest.Genesis, nil - default: - return nil, errUnknownBlock - } - } - coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { // needed when setting preference - switch { - case bytes.Equal(b, coreParentBlk.Bytes()): - return coreParentBlk, nil - case bytes.Equal(b, snowmantest.GenesisBytes): - return snowmantest.Genesis, nil - default: - return nil, errUnknownBlock - } - } - - parentBlk, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.NoError(parentBlk.Verify(ctx)) - require.NoError(parentBlk.Accept(ctx)) - - // Make sure preference is duly set - require.NoError(proVM.SetPreference(ctx, parentBlk.ID())) - require.Equal(proVM.preferred, parentBlk.ID()) - _, err = proVM.getPostForkBlock(ctx, parentBlk.ID()) - require.NoError(err) - - // Mark node as non validator - valState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - var ( - aValidator = ids.GenerateTestNodeID() - - // a validator with a weight large enough to fully fill the proposers list - weight = uint64(proposer.MaxBuildWindows * 2) - ) - return map[ids.NodeID]*validators.GetValidatorOutput{ - aValidator: { - NodeID: aValidator, - Weight: weight, - }, - }, nil - } - - coreChildBlk := snowmantest.BuildChild(coreParentBlk) - coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return coreChildBlk, nil - } - - { - // Set local clock before MaxVerifyDelay from parent timestamp. - // Check that child block is not built. - localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay - time.Second) - proVM.Set(localTime) - - _, err := proVM.BuildBlock(ctx) - require.ErrorIs(err, errProposerWindowNotStarted) - } - - { - // Set local clock exactly MaxVerifyDelay from parent timestamp. - // Check that child block is not built. - localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay) - proVM.Set(localTime) - - _, err := proVM.BuildBlock(ctx) - require.ErrorIs(err, errProposerWindowNotStarted) - } - - { - // Set local clock among MaxVerifyDelay and MaxBuildDelay from parent timestamp - // Check that child block is not built. - localTime := parentBlk.Timestamp().Add((proposer.MaxVerifyDelay + proposer.MaxBuildDelay) / 2) - proVM.Set(localTime) - - _, err := proVM.BuildBlock(ctx) - require.ErrorIs(err, errProposerWindowNotStarted) - } - - { - // Set local clock after MaxBuildDelay from parent timestamp - // Check that child block is built and it is unsigned - localTime := parentBlk.Timestamp().Add(proposer.MaxBuildDelay) - proVM.Set(localTime) - - childBlkIntf, err := proVM.BuildBlock(ctx) - require.NoError(err) - require.IsType(&postForkBlock{}, childBlkIntf) - - childBlk := childBlkIntf.(*postForkBlock) - require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block - } -} - -// We consider cases where this node is not current proposer (may be scheduled in the next future or not). -// We check that scheduler is called nonetheless, to be able to process innerVM block requests -func TestPostDurangoBuildChildResetScheduler(t *testing.T) { +// We consider cases where this node is not current proposer (may be scheduled +// in the next future or not). +// +// We check that scheduler is called nonetheless, to be able to process innerVM +// block requests. +func TestBuildChildResetScheduler(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) diff --git a/vms/proposervm/post_fork_block.go b/vms/proposervm/post_fork_block.go index b2327509d1f8..972943944d0d 100644 --- a/vms/proposervm/post_fork_block.go +++ b/vms/proposervm/post_fork_block.go @@ -19,7 +19,7 @@ type postForkBlock struct { postForkCommonComponents // slot of the proposer that produced this block. - // It is populated in verifyPostDurangoBlockDelay. + // It is populated in verifyBlockDelay. // It is used to report metrics during Accept. slot *uint64 } diff --git a/vms/proposervm/post_fork_block_test.go b/vms/proposervm/post_fork_block_test.go index be61e0fb75a6..765fbaab23ef 100644 --- a/vms/proposervm/post_fork_block_test.go +++ b/vms/proposervm/post_fork_block_test.go @@ -86,98 +86,7 @@ func TestOracle_PostForkBlock_ImplementsInterface(t *testing.T) { } // ProposerBlock.Verify tests section -func TestBlockVerify_PostForkBlock_PreDurango_ParentChecks(t *testing.T) { - require := require.New(t) - - var ( - activationTime = time.Unix(0, 0) - durangoTime = mockable.MaxTime // pre Durango - ) - coreVM, valState, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) - defer func() { - require.NoError(proVM.Shutdown(context.Background())) - }() - - pChainHeight := uint64(100) - valState.GetCurrentHeightF = func(context.Context) (uint64, error) { - return pChainHeight, nil - } - - // create parent block ... - parentCoreBlk := snowmantest.BuildChild(snowmantest.Genesis) - coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return parentCoreBlk, nil - } - coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { - switch blkID { - case snowmantest.GenesisID: - return snowmantest.Genesis, nil - case parentCoreBlk.ID(): - return parentCoreBlk, nil - default: - return nil, database.ErrNotFound - } - } - coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { - switch { - case bytes.Equal(b, snowmantest.GenesisBytes): - return snowmantest.Genesis, nil - case bytes.Equal(b, parentCoreBlk.Bytes()): - return parentCoreBlk, nil - default: - return nil, errUnknownBlock - } - } - - parentBlk, err := proVM.BuildBlock(context.Background()) - require.NoError(err) - - require.NoError(parentBlk.Verify(context.Background())) - require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) - - // .. create child block ... - childCoreBlk := snowmantest.BuildChild(parentCoreBlk) - childBlk := postForkBlock{ - postForkCommonComponents: postForkCommonComponents{ - vm: proVM, - innerBlk: childCoreBlk, - }, - } - - // set proVM to be able to build unsigned blocks - proVM.Set(proVM.Time().Add(proposer.MaxVerifyDelay)) - - { - // child block referring unknown parent does not verify - childSlb, err := block.BuildUnsigned( - ids.Empty, // refer unknown parent - proVM.Time(), - pChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childBlk.SignedBlock = childSlb - - err = childBlk.Verify(context.Background()) - require.ErrorIs(err, database.ErrNotFound) - } - - { - // child block referring known parent does verify - childSlb, err := block.BuildUnsigned( - parentBlk.ID(), // refer known parent - proVM.Time(), - pChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childBlk.SignedBlock = childSlb - - require.NoError(childBlk.Verify(context.Background())) - } -} - -func TestBlockVerify_PostForkBlock_PostDurango_ParentChecks(t *testing.T) { +func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { require := require.New(t) var ( diff --git a/vms/proposervm/proposer/windower.go b/vms/proposervm/proposer/windower.go index 2074561eede6..16641eb80b54 100644 --- a/vms/proposervm/proposer/windower.go +++ b/vms/proposervm/proposer/windower.go @@ -14,21 +14,13 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/sampler" "github.com/ava-labs/avalanchego/utils/wrappers" ) // Proposer list constants const ( - WindowDuration = 5 * time.Second - - MaxVerifyWindows = 6 - MaxVerifyDelay = MaxVerifyWindows * WindowDuration // 30 seconds - - MaxBuildWindows = 60 - MaxBuildDelay = MaxBuildWindows * WindowDuration // 5 minutes - + WindowDuration = 5 * time.Second MaxLookAheadSlots = 720 MaxLookAheadWindow = MaxLookAheadSlots * WindowDuration // 1 hour ) @@ -41,33 +33,9 @@ var ( ) type Windower interface { - // Proposers returns the proposer list for building a block at [blockHeight] - // when the validator set is defined at [pChainHeight]. The list is returned - // in order. The minimum delay of a validator is the index they appear times - // [WindowDuration]. - Proposers( - ctx context.Context, - blockHeight, - pChainHeight uint64, - maxWindows int, - ) ([]ids.NodeID, error) - - // Delay returns the amount of time that [validatorID] must wait before - // building a block at [blockHeight] when the validator set is defined at - // [pChainHeight]. - Delay( - ctx context.Context, - blockHeight, - pChainHeight uint64, - validatorID ids.NodeID, - maxWindows int, - ) (time.Duration, error) - - // In the Post-Durango windowing scheme, every validator active at - // [pChainHeight] gets specific slots it can propose in (instead of being - // able to propose from a given time on as it happens Pre-Durango). - // [ExpectedProposer] calculates which nodeID is scheduled to propose a - // block of height [blockHeight] at [slot]. + // ExpectedProposer calculates which nodeID is scheduled to propose for a + // blockHeight at a slot. + // // If no validators are currently available, [ErrAnyoneCanPropose] is // returned. ExpectedProposer( @@ -77,13 +45,12 @@ type Windower interface { slot uint64, ) (ids.NodeID, error) - // In the Post-Durango windowing scheme, every validator active at - // [pChainHeight] gets specific slots it can propose in (instead of being - // able to propose from a given time on as it happens Pre-Durango). - // [MinDelayForProposer] specifies how long [nodeID] needs to wait for its - // slot to start. Delay is specified as starting from slot zero start. - // (which is parent timestamp). For efficiency reasons, we cap the slot - // search to [MaxLookAheadSlots]. + // MinDelayForProposer calculates how long nodeID needs to wait for its slot + // to start. Delay is specified as starting from slot zero (which is the + // parent timestamp). + // + // For efficiency reasons, the search is capped to [MaxLookAheadSlots]. + // // If no validators are currently available, [ErrAnyoneCanPropose] is // returned. MinDelayForProposer( @@ -112,58 +79,6 @@ func New(state validators.State, subnetID, chainID ids.ID) Windower { } } -func (w *windower) Proposers(ctx context.Context, blockHeight, pChainHeight uint64, maxWindows int) ([]ids.NodeID, error) { - // Note: The 32-bit prng is used here for legacy reasons. All other usages - // of a prng in this file should use the 64-bit version. - source := prng.NewMT19937() - sampler, validators, err := w.makeSampler(ctx, pChainHeight, source) - if err != nil { - return nil, err - } - - var totalWeight uint64 - for _, validator := range validators { - totalWeight, err = math.Add(totalWeight, validator.weight) - if err != nil { - return nil, err - } - } - - source.Seed(w.chainSource ^ blockHeight) - - numToSample := int(min(uint64(maxWindows), totalWeight)) - indices, ok := sampler.Sample(numToSample) - if !ok { - return nil, ErrUnexpectedSamplerFailure - } - - nodeIDs := make([]ids.NodeID, numToSample) - for i, index := range indices { - nodeIDs[i] = validators[index].id - } - return nodeIDs, nil -} - -func (w *windower) Delay(ctx context.Context, blockHeight, pChainHeight uint64, validatorID ids.NodeID, maxWindows int) (time.Duration, error) { - if validatorID == ids.EmptyNodeID { - return time.Duration(maxWindows) * WindowDuration, nil - } - - proposers, err := w.Proposers(ctx, blockHeight, pChainHeight, maxWindows) - if err != nil { - return 0, err - } - - delay := time.Duration(0) - for _, nodeID := range proposers { - if nodeID == validatorID { - return delay, nil - } - delay += WindowDuration - } - return delay, nil -} - func (w *windower) ExpectedProposer( ctx context.Context, blockHeight, diff --git a/vms/proposervm/proposer/windower_test.go b/vms/proposervm/proposer/windower_test.go index 107f8c2c81f5..e95fb4ec9230 100644 --- a/vms/proposervm/proposer/windower_test.go +++ b/vms/proposervm/proposer/windower_test.go @@ -56,142 +56,21 @@ func TestWindowerNoValidators(t *testing.T) { nodeID = ids.GenerateTestNodeID() slot uint64 = 1 ) - delay, err := w.Delay(context.Background(), chainHeight, pChainHeight, nodeID, MaxVerifyWindows) - require.NoError(err) - require.Zero(delay) - proposer, err := w.ExpectedProposer(context.Background(), chainHeight, pChainHeight, slot) require.ErrorIs(err, ErrAnyoneCanPropose) require.Equal(ids.EmptyNodeID, proposer) - delay, err = w.MinDelayForProposer(context.Background(), chainHeight, pChainHeight, nodeID, slot) + delay, err := w.MinDelayForProposer(context.Background(), chainHeight, pChainHeight, nodeID, slot) require.ErrorIs(err, ErrAnyoneCanPropose) require.Zero(delay) }) } } -func TestWindowerRepeatedValidator(t *testing.T) { - require := require.New(t) - - var ( - validatorID = ids.GenerateTestNodeID() - nonValidatorID = ids.GenerateTestNodeID() - ) - - vdrState := &validatorstest.State{ - T: t, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return map[ids.NodeID]*validators.GetValidatorOutput{ - validatorID: { - NodeID: validatorID, - Weight: 10, - }, - }, nil - }, - } - - w := New(vdrState, subnetID, randomChainID) - - validatorDelay, err := w.Delay(context.Background(), 1, 0, validatorID, MaxVerifyWindows) - require.NoError(err) - require.Zero(validatorDelay) - - nonValidatorDelay, err := w.Delay(context.Background(), 1, 0, nonValidatorID, MaxVerifyWindows) - require.NoError(err) - require.Equal(MaxVerifyDelay, nonValidatorDelay) -} - -func TestDelayChangeByHeight(t *testing.T) { - require := require.New(t) - - validatorIDs, vdrState := makeValidators(t, MaxVerifyWindows) - w := New(vdrState, subnetID, fixedChainID) - - expectedDelays1 := []time.Duration{ - 2 * WindowDuration, - 5 * WindowDuration, - 3 * WindowDuration, - 4 * WindowDuration, - 0 * WindowDuration, - 1 * WindowDuration, - } - for i, expectedDelay := range expectedDelays1 { - vdrID := validatorIDs[i] - validatorDelay, err := w.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) - require.NoError(err) - require.Equal(expectedDelay, validatorDelay) - } - - expectedDelays2 := []time.Duration{ - 5 * WindowDuration, - 1 * WindowDuration, - 3 * WindowDuration, - 4 * WindowDuration, - 0 * WindowDuration, - 2 * WindowDuration, - } - for i, expectedDelay := range expectedDelays2 { - vdrID := validatorIDs[i] - validatorDelay, err := w.Delay(context.Background(), 2, 0, vdrID, MaxVerifyWindows) - require.NoError(err) - require.Equal(expectedDelay, validatorDelay) - } -} - -func TestDelayChangeByChain(t *testing.T) { - require := require.New(t) - - source := rand.NewSource(int64(0)) - rng := rand.New(source) // #nosec G404 - - chainID0 := ids.Empty - _, err := rng.Read(chainID0[:]) - require.NoError(err) - - chainID1 := ids.Empty - _, err = rng.Read(chainID1[:]) - require.NoError(err) - - validatorIDs, vdrState := makeValidators(t, MaxVerifyWindows) - w0 := New(vdrState, subnetID, chainID0) - w1 := New(vdrState, subnetID, chainID1) - - expectedDelays0 := []time.Duration{ - 5 * WindowDuration, - 2 * WindowDuration, - 0 * WindowDuration, - 3 * WindowDuration, - 1 * WindowDuration, - 4 * WindowDuration, - } - for i, expectedDelay := range expectedDelays0 { - vdrID := validatorIDs[i] - validatorDelay, err := w0.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) - require.NoError(err) - require.Equal(expectedDelay, validatorDelay) - } - - expectedDelays1 := []time.Duration{ - 0 * WindowDuration, - 1 * WindowDuration, - 4 * WindowDuration, - 5 * WindowDuration, - 3 * WindowDuration, - 2 * WindowDuration, - } - for i, expectedDelay := range expectedDelays1 { - vdrID := validatorIDs[i] - validatorDelay, err := w1.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) - require.NoError(err) - require.Equal(expectedDelay, validatorDelay) - } -} - func TestExpectedProposerChangeByHeight(t *testing.T) { require := require.New(t) - validatorIDs, vdrState := makeValidators(t, 10) + validatorIDs, vdrState := makeValidators(t) w := New(vdrState, subnetID, fixedChainID) var ( @@ -226,7 +105,7 @@ func TestExpectedProposerChangeByChain(t *testing.T) { _, err = rng.Read(chainID1[:]) require.NoError(err) - validatorIDs, vdrState := makeValidators(t, 10) + validatorIDs, vdrState := makeValidators(t) var ( dummyCtx = context.Background() @@ -251,7 +130,7 @@ func TestExpectedProposerChangeByChain(t *testing.T) { func TestExpectedProposerChangeBySlot(t *testing.T) { require := require.New(t) - validatorIDs, vdrState := makeValidators(t, 10) + validatorIDs, vdrState := makeValidators(t) w := New(vdrState, subnetID, fixedChainID) var ( @@ -304,7 +183,7 @@ func TestExpectedProposerChangeBySlot(t *testing.T) { func TestCoherenceOfExpectedProposerAndMinDelayForProposer(t *testing.T) { require := require.New(t) - _, vdrState := makeValidators(t, 10) + _, vdrState := makeValidators(t) w := New(vdrState, subnetID, fixedChainID) var ( @@ -328,7 +207,7 @@ func TestCoherenceOfExpectedProposerAndMinDelayForProposer(t *testing.T) { func TestMinDelayForProposer(t *testing.T) { require := require.New(t) - validatorIDs, vdrState := makeValidators(t, 10) + validatorIDs, vdrState := makeValidators(t) w := New(vdrState, subnetID, fixedChainID) var ( @@ -362,7 +241,7 @@ func TestMinDelayForProposer(t *testing.T) { func BenchmarkMinDelayForProposer(b *testing.B) { require := require.New(b) - _, vdrState := makeValidators(b, 10) + _, vdrState := makeValidators(b) w := New(vdrState, subnetID, fixedChainID) var ( @@ -420,7 +299,7 @@ func TestTimeToSlot(t *testing.T) { func TestProposerDistribution(t *testing.T) { require := require.New(t) - validatorIDs, vdrState := makeValidators(t, 10) + validatorIDs, vdrState := makeValidators(t) w := New(vdrState, subnetID, fixedChainID) var ( @@ -466,8 +345,8 @@ func TestProposerDistribution(t *testing.T) { require.Less(maxSTDDeviation, 3.) } -func makeValidators(t testing.TB, count int) ([]ids.NodeID, *validatorstest.State) { - validatorIDs := make([]ids.NodeID, count) +func makeValidators(t testing.TB) ([]ids.NodeID, *validatorstest.State) { + validatorIDs := make([]ids.NodeID, 10) for i := range validatorIDs { validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) } @@ -478,7 +357,7 @@ func makeValidatorState(t testing.TB, validatorIDs []ids.NodeID) *validatorstest vdrState := &validatorstest.State{ T: t, GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) + vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, len(validatorIDs)) for _, id := range validatorIDs { vdrs[id] = &validators.GetValidatorOutput{ NodeID: id, diff --git a/vms/proposervm/vm.go b/vms/proposervm/vm.go index 08914cb87f82..cfead7da6016 100644 --- a/vms/proposervm/vm.go +++ b/vms/proposervm/vm.go @@ -337,27 +337,15 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { var ( childBlockHeight = blk.Height() + 1 parentTimestamp = blk.Timestamp() - nextStartTime time.Time + currentTime = vm.Clock.Time().Truncate(time.Second) + ) + nextStartTime, err := vm.getSlotTime( + ctx, + childBlockHeight, + pChainHeight, + proposer.TimeToSlot(parentTimestamp, currentTime), + parentTimestamp, ) - if vm.Upgrades.IsDurangoActivated(parentTimestamp) { - currentTime := vm.Clock.Time().Truncate(time.Second) - if nextStartTime, err = vm.getPostDurangoSlotTime( - ctx, - childBlockHeight, - pChainHeight, - proposer.TimeToSlot(parentTimestamp, currentTime), - parentTimestamp, - ); err == nil { - vm.proposerBuildSlotGauge.Set(float64(proposer.TimeToSlot(parentTimestamp, nextStartTime))) - } - } else { - nextStartTime, err = vm.getPreDurangoSlotTime( - ctx, - childBlockHeight, - pChainHeight, - parentTimestamp, - ) - } if err != nil { vm.ctx.Log.Debug("failed to fetch the expected delay", zap.Error(err), @@ -369,6 +357,8 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { // until the P-chain's height has advanced. return nil } + + vm.proposerBuildSlotGauge.Set(float64(proposer.TimeToSlot(parentTimestamp, nextStartTime))) vm.Scheduler.SetBuildBlockTime(nextStartTime) vm.ctx.Log.Debug("set preference", @@ -379,28 +369,7 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { return nil } -func (vm *VM) getPreDurangoSlotTime( - ctx context.Context, - blkHeight, - pChainHeight uint64, - parentTimestamp time.Time, -) (time.Time, error) { - delay, err := vm.Windower.Delay(ctx, blkHeight, pChainHeight, vm.ctx.NodeID, proposer.MaxBuildWindows) - if err != nil { - return time.Time{}, err - } - - // Note: The P-chain does not currently try to target any block time. It - // notifies the consensus engine as soon as a new block may be built. To - // avoid fast runs of blocks there is an additional minimum delay that - // validators can specify. This delay may be an issue for high performance, - // custom VMs. Until the P-chain is modified to target a specific block - // time, ProposerMinBlockDelay can be configured in the subnet config. - delay = max(delay, vm.MinBlkDelay) - return parentTimestamp.Add(delay), nil -} - -func (vm *VM) getPostDurangoSlotTime( +func (vm *VM) getSlotTime( ctx context.Context, blkHeight, pChainHeight, diff --git a/vms/proposervm/vm_test.go b/vms/proposervm/vm_test.go index d551addc0a2b..fda274fe4197 100644 --- a/vms/proposervm/vm_test.go +++ b/vms/proposervm/vm_test.go @@ -2381,7 +2381,7 @@ func TestHistoricalBlockDeletion(t *testing.T) { requireNumHeights(newNumHistoricalBlocks) } -func TestGetPostDurangoSlotTimeWithNoValidators(t *testing.T) { +func TestGetSlotTimeWithNoValidators(t *testing.T) { require := require.New(t) var ( @@ -2435,7 +2435,7 @@ func TestGetPostDurangoSlotTimeWithNoValidators(t *testing.T) { currentTime := proVM.Clock.Time().Truncate(time.Second) parentTimestamp := statefulBlock.Timestamp() - slotTime, err := proVM.getPostDurangoSlotTime( + slotTime, err := proVM.getSlotTime( context.Background(), statefulBlock.Height()+1, statelessBlock.PChainHeight(),