diff --git a/vms/proposervm/batched_vm_test.go b/vms/proposervm/batched_vm_test.go index 326272275dac..684d91ceb3df 100644 --- a/vms/proposervm/batched_vm_test.go +++ b/vms/proposervm/batched_vm_test.go @@ -225,7 +225,7 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk1.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk1.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk2 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ @@ -235,7 +235,7 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { BytesV: []byte{2}, ParentV: coreBlk1.ID(), HeightV: coreBlk1.Height() + 1, - TimestampV: coreBlk1.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreBlk1.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -246,7 +246,7 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk2.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk2.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk3 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ @@ -431,7 +431,7 @@ func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { BytesV: []byte{3}, ParentV: coreBlk2.ID(), HeightV: coreBlk2.Height() + 1, - TimestampV: postForkTime.Add(proposer.MaxDelay), + TimestampV: postForkTime.Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -443,7 +443,7 @@ func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { // prepare build of next block require.NoError(builtBlk3.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk3.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk4 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ @@ -704,7 +704,7 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk1.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk1.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk2 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ @@ -714,7 +714,7 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { BytesV: []byte{2}, ParentV: coreBlk1.ID(), HeightV: coreBlk1.Height() + 1, - TimestampV: coreBlk1.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreBlk1.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -725,7 +725,7 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk2.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk2.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk3 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ @@ -867,7 +867,7 @@ func TestBatchedParseBlockAtSnomanPlusPlusFork(t *testing.T) { BytesV: []byte{3}, ParentV: coreBlk2.ID(), HeightV: coreBlk2.Height() + 1, - TimestampV: postForkTime.Add(proposer.MaxDelay), + TimestampV: postForkTime.Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -879,7 +879,7 @@ func TestBatchedParseBlockAtSnomanPlusPlusFork(t *testing.T) { // prepare build of next block require.NoError(builtBlk3.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk3.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxDelay)) + proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) coreBlk4 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ diff --git a/vms/proposervm/block.go b/vms/proposervm/block.go index f07362708611..489d325f8f70 100644 --- a/vms/proposervm/block.go +++ b/vms/proposervm/block.go @@ -144,7 +144,7 @@ func (p *postForkCommonComponents) Verify( childHeight := child.Height() proposerID := child.Proposer() - minDelay, err := p.vm.Windower.Delay(ctx, childHeight, parentPChainHeight, proposerID) + minDelay, err := p.vm.Windower.Delay(ctx, childHeight, parentPChainHeight, proposerID, proposer.MaxVerifyWindows) if err != nil { return err } @@ -155,7 +155,7 @@ func (p *postForkCommonComponents) Verify( } // Verify the signature of the node - shouldHaveProposer := delay < proposer.MaxDelay + shouldHaveProposer := delay < proposer.MaxVerifyDelay if err := child.SignedBlock.Verify(shouldHaveProposer, p.vm.ctx.ChainID); err != nil { return err } @@ -203,10 +203,10 @@ func (p *postForkCommonComponents) buildChild( } delay := newTimestamp.Sub(parentTimestamp) - if delay < proposer.MaxDelay { + if delay < proposer.MaxBuildDelay { parentHeight := p.innerBlk.Height() proposerID := p.vm.ctx.NodeID - minDelay, err := p.vm.Windower.Delay(ctx, parentHeight+1, parentPChainHeight, proposerID) + 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"), @@ -249,7 +249,7 @@ func (p *postForkCommonComponents) buildChild( // Build the child var statelessChild block.SignedBlock - if delay >= proposer.MaxDelay { + if delay >= proposer.MaxVerifyDelay { statelessChild, err = block.BuildUnsigned( parentID, newTimestamp, diff --git a/vms/proposervm/block_test.go b/vms/proposervm/block_test.go index 90c99a22dd21..04ac66ecf34e 100644 --- a/vms/proposervm/block_test.go +++ b/vms/proposervm/block_test.go @@ -4,6 +4,7 @@ package proposervm import ( + "bytes" "context" "crypto/ecdsa" "crypto/elliptic" @@ -17,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" @@ -53,7 +55,7 @@ func TestPostForkCommonComponents_buildChild(t *testing.T) { vdrState := validators.NewMockState(ctrl) vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() windower := proposer.NewMockWindower(ctrl) - windower.EXPECT().Delay(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(time.Duration(0), nil).AnyTimes() + windower.EXPECT().Delay(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(time.Duration(0), nil).AnyTimes() pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(err) @@ -84,3 +86,265 @@ func TestPostForkCommonComponents_buildChild(t *testing.T) { require.NoError(err) require.Equal(builtBlk, gotChild.(*postForkBlock).innerBlk) } + +func TestValidatorNodeBlockBuiltDelaysTests(t *testing.T) { + require := require.New(t) + ctx := context.Background() + + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + 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 := &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: ids.GenerateTestID(), + StatusV: choices.Processing, + }, + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, + } + coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { + return coreParentBlk, nil + } + coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { + switch { + case blkID == coreParentBlk.ID(): + return coreParentBlk, nil + case blkID == coreGenBlk.ID(): + return coreGenBlk, 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, coreGenBlk.Bytes()): + return coreGenBlk, 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 := &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: ids.GenerateTestID(), + StatusV: choices.Processing, + }, + BytesV: []byte{2}, + ParentV: coreParentBlk.ID(), + HeightV: coreParentBlk.Height() + 1, + } + 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) + + childBlk, err := proVM.BuildBlock(ctx) + require.NoError(err) + require.IsType(&postForkBlock{}, childBlk) + require.Equal(proVM.ctx.NodeID, childBlk.(*postForkBlock).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) + + childBlk, err := proVM.BuildBlock(ctx) + require.NoError(err) + require.IsType(&postForkBlock{}, childBlk) + require.Equal(ids.EmptyNodeID, childBlk.(*postForkBlock).Proposer()) // signed block + } + + { + // Set local clock among MaxVerifyDelay and MaxBuildDelay from parent timestamp + // Check that child block is unsigned + localTime := parentBlk.Timestamp().Add((proposer.MaxVerifyDelay + proposer.MaxBuildDelay) / 2) + proVM.Set(localTime) + + childBlk, err := proVM.BuildBlock(ctx) + require.NoError(err) + require.IsType(&postForkBlock{}, childBlk) + require.Equal(ids.EmptyNodeID, childBlk.(*postForkBlock).Proposer()) // unsigned so no proposer + } + + { + // Set local clock after MaxBuildDelay from parent timestamp + // Check that child block is unsigned + localTime := parentBlk.Timestamp().Add(proposer.MaxBuildDelay) + proVM.Set(localTime) + + childBlk, err := proVM.BuildBlock(ctx) + require.NoError(err) + require.IsType(&postForkBlock{}, childBlk) + require.Equal(ids.EmptyNodeID, childBlk.(*postForkBlock).Proposer()) // unsigned so no proposer + } +} + +func TestNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { + require := require.New(t) + ctx := context.Background() + + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + 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 := &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: ids.GenerateTestID(), + StatusV: choices.Processing, + }, + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, + } + coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { + return coreParentBlk, nil + } + coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { + switch { + case blkID == coreParentBlk.ID(): + return coreParentBlk, nil + case blkID == coreGenBlk.ID(): + return coreGenBlk, 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, coreGenBlk.Bytes()): + return coreGenBlk, 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 := &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: ids.GenerateTestID(), + StatusV: choices.Processing, + }, + BytesV: []byte{2}, + ParentV: coreParentBlk.ID(), + HeightV: coreParentBlk.Height() + 1, + } + 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(errProposerWindowNotStarted, err) + } + + { + // 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(errProposerWindowNotStarted, err) + } + + { + // 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(errProposerWindowNotStarted, err) + } + + { + // 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) + + childBlk, err := proVM.BuildBlock(ctx) + require.NoError(err) + require.IsType(&postForkBlock{}, childBlk) + require.Equal(ids.EmptyNodeID, childBlk.(*postForkBlock).Proposer()) // unsigned so no proposer + } +} diff --git a/vms/proposervm/post_fork_block_test.go b/vms/proposervm/post_fork_block_test.go index b2b36073335d..659bdd1e5fd3 100644 --- a/vms/proposervm/post_fork_block_test.go +++ b/vms/proposervm/post_fork_block_test.go @@ -138,7 +138,7 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { } } - proVM.Set(proVM.Time().Add(proposer.MaxDelay)) + proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) prntProBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -177,7 +177,7 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { // child block referring known parent does verify childSlb, err = block.BuildUnsigned( prntProBlk.ID(), // refer known parent - prntProBlk.Timestamp().Add(proposer.MaxDelay), + prntProBlk.Timestamp().Add(proposer.MaxVerifyDelay), pChainHeight, childCoreBlk.Bytes(), ) @@ -185,7 +185,7 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { childProBlk.SignedBlock = childSlb require.NoError(err) - proVM.Set(proVM.Time().Add(proposer.MaxDelay)) + proVM.Set(proVM.Time().Add(proposer.MaxVerifyDelay)) require.NoError(childProBlk.Verify(context.Background())) } @@ -210,7 +210,7 @@ func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { }, BytesV: []byte{1}, ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return prntCoreBlk, nil @@ -279,7 +279,7 @@ func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { require.ErrorIs(err, errTimeNotMonotonic) // block cannot arrive before its creator window starts - blkWinDelay, err := proVM.Delay(context.Background(), childCoreBlk.Height(), pChainHeight, proVM.ctx.NodeID) + blkWinDelay, err := proVM.Delay(context.Background(), childCoreBlk.Height(), pChainHeight, proVM.ctx.NodeID, proposer.MaxVerifyWindows) require.NoError(err) beforeWinStart := prntTimestamp.Add(blkWinDelay).Add(-1 * time.Second) proVM.Clock.Set(beforeWinStart) @@ -332,7 +332,7 @@ func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { require.NoError(childProBlk.Verify(context.Background())) // block can arrive within submission window - atSubWindowEnd := proVM.Time().Add(proposer.MaxDelay) + atSubWindowEnd := proVM.Time().Add(proposer.MaxVerifyDelay) proVM.Clock.Set(atSubWindowEnd) childSlb, err = block.BuildUnsigned( prntProBlk.ID(), @@ -382,7 +382,7 @@ func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { }, BytesV: []byte{1}, ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return prntCoreBlk, nil @@ -423,7 +423,7 @@ func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { }, ParentV: prntCoreBlk.ID(), BytesV: []byte{2}, - TimestampV: prntProBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: prntProBlk.Timestamp().Add(proposer.MaxVerifyDelay), } // child P-Chain height must not precede parent P-Chain height @@ -522,7 +522,7 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) }, BytesV: []byte{1}, ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), }, } oracleCoreBlk.opts = [2]snowman.Block{ @@ -603,7 +603,7 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) }, ParentV: oracleCoreBlk.opts[0].ID(), BytesV: []byte{2}, - TimestampV: parentBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: parentBlk.Timestamp().Add(proposer.MaxVerifyDelay), } // child P-Chain height must not precede parent P-Chain height @@ -701,7 +701,7 @@ func TestBlockVerify_PostForkBlock_CoreBlockVerifyIsCalledOnce(t *testing.T) { }, BytesV: []byte{1}, ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -764,7 +764,7 @@ func TestBlockAccept_PostForkBlock_SetsLastAcceptedBlock(t *testing.T) { }, BytesV: []byte{1}, ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -829,7 +829,7 @@ func TestBlockAccept_PostForkBlock_TwoProBlocksWithSameCoreBlock_OneIsAccepted(t BytesV: []byte{1}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -871,7 +871,7 @@ func TestBlockReject_PostForkBlock_InnerBlockIsNotRejected(t *testing.T) { BytesV: []byte{1}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil diff --git a/vms/proposervm/post_fork_option_test.go b/vms/proposervm/post_fork_option_test.go index 09fe29730b6f..8e93e2aad05f 100644 --- a/vms/proposervm/post_fork_option_test.go +++ b/vms/proposervm/post_fork_option_test.go @@ -135,7 +135,7 @@ func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { }, ParentV: oracleCoreBlk.opts[0].ID(), BytesV: []byte{4}, - TimestampV: oracleCoreBlk.opts[0].Timestamp().Add(proposer.MaxDelay), + TimestampV: oracleCoreBlk.opts[0].Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return childCoreBlk, nil diff --git a/vms/proposervm/pre_fork_block_test.go b/vms/proposervm/pre_fork_block_test.go index 3a28a9568b5c..1366482a0d9b 100644 --- a/vms/proposervm/pre_fork_block_test.go +++ b/vms/proposervm/pre_fork_block_test.go @@ -275,7 +275,7 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { } } - proVM.Set(proVM.Time().Add(proposer.MaxDelay)) + proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) prntProBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -286,7 +286,7 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { StatusV: choices.Processing, }, BytesV: []byte{2}, - TimestampV: prntCoreBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: prntCoreBlk.Timestamp().Add(proposer.MaxVerifyDelay), } childProBlk := preForkBlock{ Block: childCoreBlk, diff --git a/vms/proposervm/proposer/mock_windower.go b/vms/proposervm/proposer/mock_windower.go index bfa83998995c..3e7375326429 100644 --- a/vms/proposervm/proposer/mock_windower.go +++ b/vms/proposervm/proposer/mock_windower.go @@ -40,31 +40,31 @@ func (m *MockWindower) EXPECT() *MockWindowerMockRecorder { } // Delay mocks base method. -func (m *MockWindower) Delay(arg0 context.Context, arg1, arg2 uint64, arg3 ids.NodeID) (time.Duration, error) { +func (m *MockWindower) Delay(arg0 context.Context, arg1, arg2 uint64, arg3 ids.NodeID, arg4 int) (time.Duration, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delay", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Delay", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(time.Duration) ret1, _ := ret[1].(error) return ret0, ret1 } // Delay indicates an expected call of Delay. -func (mr *MockWindowerMockRecorder) Delay(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockWindowerMockRecorder) Delay(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delay", reflect.TypeOf((*MockWindower)(nil).Delay), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delay", reflect.TypeOf((*MockWindower)(nil).Delay), arg0, arg1, arg2, arg3, arg4) } // Proposers mocks base method. -func (m *MockWindower) Proposers(arg0 context.Context, arg1, arg2 uint64) ([]ids.NodeID, error) { +func (m *MockWindower) Proposers(arg0 context.Context, arg1, arg2 uint64, arg3 int) ([]ids.NodeID, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Proposers", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Proposers", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]ids.NodeID) ret1, _ := ret[1].(error) return ret0, ret1 } // Proposers indicates an expected call of Proposers. -func (mr *MockWindowerMockRecorder) Proposers(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockWindowerMockRecorder) Proposers(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proposers", reflect.TypeOf((*MockWindower)(nil).Proposers), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proposers", reflect.TypeOf((*MockWindower)(nil).Proposers), arg0, arg1, arg2, arg3) } diff --git a/vms/proposervm/proposer/windower.go b/vms/proposervm/proposer/windower.go index 4f67b27903ea..8cb7bc43b24d 100644 --- a/vms/proposervm/proposer/windower.go +++ b/vms/proposervm/proposer/windower.go @@ -17,9 +17,13 @@ import ( // Proposer list constants const ( - MaxWindows = 6 WindowDuration = 5 * time.Second - MaxDelay = MaxWindows * WindowDuration + + MaxVerifyWindows = 6 + MaxVerifyDelay = MaxVerifyWindows * WindowDuration // 30 seconds + + MaxBuildWindows = 60 + MaxBuildDelay = MaxBuildWindows * WindowDuration // 5 minutes ) var _ Windower = (*windower)(nil) @@ -33,6 +37,7 @@ type Windower interface { ctx context.Context, chainHeight, pChainHeight uint64, + maxWindows int, ) ([]ids.NodeID, error) // Delay returns the amount of time that [validatorID] must wait before // building a block at [chainHeight] when the validator set is defined at @@ -42,6 +47,7 @@ type Windower interface { chainHeight, pChainHeight uint64, validatorID ids.NodeID, + maxWindows int, ) (time.Duration, error) } @@ -64,7 +70,7 @@ func New(state validators.State, subnetID, chainID ids.ID) Windower { } } -func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint64) ([]ids.NodeID, error) { +func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint64, maxWindows int) ([]ids.NodeID, error) { // get the validator set by the p-chain height validatorsMap, err := w.state.GetValidatorSet(ctx, pChainHeight, w.subnetID) if err != nil { @@ -101,7 +107,7 @@ func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint return nil, err } - numToSample := MaxWindows + numToSample := maxWindows if weight < uint64(numToSample) { numToSample = int(weight) } @@ -121,12 +127,12 @@ func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint return nodeIDs, nil } -func (w *windower) Delay(ctx context.Context, chainHeight, pChainHeight uint64, validatorID ids.NodeID) (time.Duration, error) { +func (w *windower) Delay(ctx context.Context, chainHeight, pChainHeight uint64, validatorID ids.NodeID, maxWindows int) (time.Duration, error) { if validatorID == ids.EmptyNodeID { - return MaxDelay, nil + return time.Duration(maxWindows) * WindowDuration, nil } - proposers, err := w.Proposers(ctx, chainHeight, pChainHeight) + proposers, err := w.Proposers(ctx, chainHeight, pChainHeight, maxWindows) if err != nil { return 0, err } diff --git a/vms/proposervm/proposer/windower_test.go b/vms/proposervm/proposer/windower_test.go index 961398c78867..2a141a361ea2 100644 --- a/vms/proposervm/proposer/windower_test.go +++ b/vms/proposervm/proposer/windower_test.go @@ -30,7 +30,7 @@ func TestWindowerNoValidators(t *testing.T) { w := New(vdrState, subnetID, chainID) - delay, err := w.Delay(context.Background(), 1, 0, nodeID) + delay, err := w.Delay(context.Background(), 1, 0, nodeID, MaxVerifyWindows) require.NoError(err) require.Zero(delay) } @@ -56,13 +56,13 @@ func TestWindowerRepeatedValidator(t *testing.T) { w := New(vdrState, subnetID, chainID) - validatorDelay, err := w.Delay(context.Background(), 1, 0, validatorID) + 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) + nonValidatorDelay, err := w.Delay(context.Background(), 1, 0, nonValidatorID, MaxVerifyWindows) require.NoError(err) - require.Equal(MaxDelay, nonValidatorDelay) + require.Equal(MaxVerifyDelay, nonValidatorDelay) } func TestWindowerChangeByHeight(t *testing.T) { @@ -70,14 +70,14 @@ func TestWindowerChangeByHeight(t *testing.T) { subnetID := ids.ID{0, 1} chainID := ids.ID{0, 2} - validatorIDs := make([]ids.NodeID, MaxWindows) + validatorIDs := make([]ids.NodeID, MaxVerifyWindows) for i := range validatorIDs { validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) } vdrState := &validators.TestState{ T: t, GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxWindows) + vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) for _, id := range validatorIDs { vdrs[id] = &validators.GetValidatorOutput{ NodeID: id, @@ -100,7 +100,7 @@ func TestWindowerChangeByHeight(t *testing.T) { } for i, expectedDelay := range expectedDelays1 { vdrID := validatorIDs[i] - validatorDelay, err := w.Delay(context.Background(), 1, 0, vdrID) + validatorDelay, err := w.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) require.NoError(err) require.Equal(expectedDelay, validatorDelay) } @@ -115,7 +115,7 @@ func TestWindowerChangeByHeight(t *testing.T) { } for i, expectedDelay := range expectedDelays2 { vdrID := validatorIDs[i] - validatorDelay, err := w.Delay(context.Background(), 2, 0, vdrID) + validatorDelay, err := w.Delay(context.Background(), 2, 0, vdrID, MaxVerifyWindows) require.NoError(err) require.Equal(expectedDelay, validatorDelay) } @@ -132,14 +132,14 @@ func TestWindowerChangeByChain(t *testing.T) { chainID1 := ids.ID{} _, _ = rand.Read(chainID1[:]) // #nosec G404 - validatorIDs := make([]ids.NodeID, MaxWindows) + validatorIDs := make([]ids.NodeID, MaxVerifyWindows) for i := range validatorIDs { validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) } vdrState := &validators.TestState{ T: t, GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxWindows) + vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) for _, id := range validatorIDs { vdrs[id] = &validators.GetValidatorOutput{ NodeID: id, @@ -163,7 +163,7 @@ func TestWindowerChangeByChain(t *testing.T) { } for i, expectedDelay := range expectedDelays0 { vdrID := validatorIDs[i] - validatorDelay, err := w0.Delay(context.Background(), 1, 0, vdrID) + validatorDelay, err := w0.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) require.NoError(err) require.Equal(expectedDelay, validatorDelay) } @@ -178,7 +178,7 @@ func TestWindowerChangeByChain(t *testing.T) { } for i, expectedDelay := range expectedDelays1 { vdrID := validatorIDs[i] - validatorDelay, err := w1.Delay(context.Background(), 1, 0, vdrID) + validatorDelay, err := w1.Delay(context.Background(), 1, 0, vdrID, MaxVerifyWindows) require.NoError(err) require.Equal(expectedDelay, validatorDelay) } diff --git a/vms/proposervm/vm.go b/vms/proposervm/vm.go index ae9691ab380e..a7bb897932d3 100644 --- a/vms/proposervm/vm.go +++ b/vms/proposervm/vm.go @@ -200,10 +200,10 @@ func (vm *VM) Initialize( vm.State = baseState vm.Windower = proposer.New(chainCtx.ValidatorState, chainCtx.SubnetID, chainCtx.ChainID) vm.Tree = tree.New() - innerBlkCache, err := metercacher.New[ids.ID, snowman.Block]( + innerBlkCache, err := metercacher.New( "inner_block_cache", registerer, - cache.NewSizedLRU[ids.ID, snowman.Block]( + cache.NewSizedLRU( innerBlkCacheSize, cachedBlockSize, ), @@ -355,7 +355,7 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { } // reset scheduler - minDelay, err := vm.Windower.Delay(ctx, blk.Height()+1, pChainHeight, vm.ctx.NodeID) + minDelay, err := vm.Windower.Delay(ctx, blk.Height()+1, pChainHeight, vm.ctx.NodeID, proposer.MaxBuildWindows) if err != nil { vm.ctx.Log.Debug("failed to fetch the expected delay", zap.Error(err), diff --git a/vms/proposervm/vm_byzantine_test.go b/vms/proposervm/vm_byzantine_test.go index c53830077bca..dcfebf847cf3 100644 --- a/vms/proposervm/vm_byzantine_test.go +++ b/vms/proposervm/vm_byzantine_test.go @@ -47,7 +47,7 @@ func TestInvalidByzantineProposerParent(t *testing.T) { BytesV: []byte{1}, ParentV: gBlock.ID(), HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp().Add(proposer.MaxDelay), + TimestampV: gBlock.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return xBlock, nil @@ -70,7 +70,7 @@ func TestInvalidByzantineProposerParent(t *testing.T) { BytesV: yBlockBytes, ParentV: xBlock.ID(), HeightV: xBlock.Height() + 1, - TimestampV: xBlock.Timestamp().Add(proposer.MaxDelay), + TimestampV: xBlock.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.ParseBlockF = func(_ context.Context, blockBytes []byte) (snowman.Block, error) { @@ -225,7 +225,7 @@ func TestInvalidByzantineProposerPreForkParent(t *testing.T) { BytesV: []byte{1}, ParentV: gBlock.ID(), HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp().Add(proposer.MaxDelay), + TimestampV: gBlock.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return xBlock, nil @@ -240,7 +240,7 @@ func TestInvalidByzantineProposerPreForkParent(t *testing.T) { BytesV: yBlockBytes, ParentV: xBlock.ID(), HeightV: xBlock.Height() + 1, - TimestampV: xBlock.Timestamp().Add(proposer.MaxDelay), + TimestampV: xBlock.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { diff --git a/vms/proposervm/vm_test.go b/vms/proposervm/vm_test.go index b75493f26ac6..fb8672d8b2f4 100644 --- a/vms/proposervm/vm_test.go +++ b/vms/proposervm/vm_test.go @@ -233,7 +233,7 @@ func TestBuildBlockTimestampAreRoundedToSeconds(t *testing.T) { BytesV: []byte{1}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -263,7 +263,7 @@ func TestBuildBlockIsIdempotent(t *testing.T) { BytesV: []byte{1}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -298,7 +298,7 @@ func TestFirstProposerBlockIsBuiltOnTopOfGenesis(t *testing.T) { BytesV: []byte{1}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -403,7 +403,7 @@ func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { return coreBlk3, nil } - proVM.Set(proVM.Time().Add(proposer.MaxDelay)) + proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) builtBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -498,7 +498,7 @@ func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { return coreBlk3, nil } - proVM.Set(proVM.Time().Add(proposer.MaxDelay)) + proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) blk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -720,7 +720,7 @@ func TestPreFork_BuildBlock(t *testing.T) { BytesV: []byte{3}, ParentV: coreGenBlk.ID(), HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxDelay), + TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -1021,7 +1021,7 @@ func TestExpiredBuildBlock(t *testing.T) { _, err = proVM.BuildBlock(context.Background()) require.ErrorIs(err, errProposerWindowNotStarted) - proVM.Set(statelessBlock.Timestamp().Add(proposer.MaxDelay)) + proVM.Set(statelessBlock.Timestamp().Add(proposer.MaxVerifyDelay)) proVM.Scheduler.SetBuildBlockTime(time.Now()) // The engine should have been notified to attempt to build a block now that @@ -1604,7 +1604,7 @@ func TestTooFarAdvanced(t *testing.T) { ySlb, err = statelessblock.BuildUnsigned( aBlock.ID(), - aBlock.Timestamp().Add(proposer.MaxDelay), + aBlock.Timestamp().Add(proposer.MaxVerifyDelay), defaultPChainHeight, yBlock.Bytes(), )