github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/block_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package proposervm 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/ecdsa" 10 "crypto/elliptic" 11 "crypto/rand" 12 "testing" 13 "time" 14 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/stretchr/testify/require" 17 "go.uber.org/mock/gomock" 18 19 "github.com/MetalBlockchain/metalgo/ids" 20 "github.com/MetalBlockchain/metalgo/snow" 21 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 22 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest" 23 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 24 "github.com/MetalBlockchain/metalgo/snow/validators" 25 "github.com/MetalBlockchain/metalgo/staking" 26 "github.com/MetalBlockchain/metalgo/utils/logging" 27 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 28 "github.com/MetalBlockchain/metalgo/vms/proposervm/proposer" 29 "github.com/MetalBlockchain/metalgo/vms/proposervm/scheduler" 30 ) 31 32 // Assert that when the underlying VM implements ChainVMWithBuildBlockContext 33 // and the proposervm is activated, we call the VM's BuildBlockWithContext 34 // method to build a block rather than BuildBlockWithContext. If the proposervm 35 // isn't activated, we should call BuildBlock rather than BuildBlockWithContext. 36 func TestPostForkCommonComponents_buildChild(t *testing.T) { 37 require := require.New(t) 38 ctrl := gomock.NewController(t) 39 40 var ( 41 nodeID = ids.GenerateTestNodeID() 42 pChainHeight uint64 = 1337 43 parentID = ids.GenerateTestID() 44 parentTimestamp = time.Now().Truncate(time.Second) 45 parentHeight uint64 = 1234 46 blkID = ids.GenerateTestID() 47 ) 48 49 innerBlk := snowmantest.NewMockBlock(ctrl) 50 innerBlk.EXPECT().ID().Return(blkID).AnyTimes() 51 innerBlk.EXPECT().Height().Return(parentHeight + 1).AnyTimes() 52 53 builtBlk := snowmantest.NewMockBlock(ctrl) 54 builtBlk.EXPECT().Bytes().Return([]byte{1, 2, 3}).AnyTimes() 55 builtBlk.EXPECT().ID().Return(ids.GenerateTestID()).AnyTimes() 56 builtBlk.EXPECT().Height().Return(pChainHeight).AnyTimes() 57 58 innerVM := block.NewMockChainVM(ctrl) 59 innerBlockBuilderVM := block.NewMockBuildBlockWithContextChainVM(ctrl) 60 innerBlockBuilderVM.EXPECT().BuildBlockWithContext(gomock.Any(), &block.Context{ 61 PChainHeight: pChainHeight - 1, 62 }).Return(builtBlk, nil).AnyTimes() 63 64 vdrState := validators.NewMockState(ctrl) 65 vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() 66 67 windower := proposer.NewMockWindower(ctrl) 68 windower.EXPECT().ExpectedProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nodeID, nil).AnyTimes() 69 70 pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 71 require.NoError(err) 72 vm := &VM{ 73 Config: Config{ 74 ActivationTime: time.Unix(0, 0), 75 DurangoTime: time.Unix(0, 0), 76 StakingCertLeaf: &staking.Certificate{}, 77 StakingLeafSigner: pk, 78 Registerer: prometheus.NewRegistry(), 79 }, 80 ChainVM: innerVM, 81 blockBuilderVM: innerBlockBuilderVM, 82 ctx: &snow.Context{ 83 NodeID: nodeID, 84 ValidatorState: vdrState, 85 Log: logging.NoLog{}, 86 }, 87 Windower: windower, 88 } 89 90 blk := &postForkCommonComponents{ 91 innerBlk: innerBlk, 92 vm: vm, 93 } 94 95 // Should call BuildBlockWithContext since proposervm is activated 96 gotChild, err := blk.buildChild( 97 context.Background(), 98 parentID, 99 parentTimestamp, 100 pChainHeight-1, 101 ) 102 require.NoError(err) 103 require.Equal(builtBlk, gotChild.(*postForkBlock).innerBlk) 104 } 105 106 func TestPreDurangoValidatorNodeBlockBuiltDelaysTests(t *testing.T) { 107 require := require.New(t) 108 ctx := context.Background() 109 110 var ( 111 activationTime = time.Unix(0, 0) 112 durangoTime = mockable.MaxTime 113 ) 114 coreVM, valState, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 115 defer func() { 116 require.NoError(proVM.Shutdown(ctx)) 117 }() 118 119 // Build a post fork block. It'll be the parent block in our test cases 120 parentTime := time.Now().Truncate(time.Second) 121 proVM.Set(parentTime) 122 123 coreParentBlk := snowmantest.BuildChild(snowmantest.Genesis) 124 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 125 return coreParentBlk, nil 126 } 127 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 128 switch blkID { 129 case coreParentBlk.ID(): 130 return coreParentBlk, nil 131 case snowmantest.GenesisID: 132 return snowmantest.Genesis, nil 133 default: 134 return nil, errUnknownBlock 135 } 136 } 137 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { // needed when setting preference 138 switch { 139 case bytes.Equal(b, coreParentBlk.Bytes()): 140 return coreParentBlk, nil 141 case bytes.Equal(b, snowmantest.GenesisBytes): 142 return snowmantest.Genesis, nil 143 default: 144 return nil, errUnknownBlock 145 } 146 } 147 148 parentBlk, err := proVM.BuildBlock(ctx) 149 require.NoError(err) 150 require.NoError(parentBlk.Verify(ctx)) 151 require.NoError(parentBlk.Accept(ctx)) 152 153 // Make sure preference is duly set 154 require.NoError(proVM.SetPreference(ctx, parentBlk.ID())) 155 require.Equal(proVM.preferred, parentBlk.ID()) 156 _, err = proVM.getPostForkBlock(ctx, parentBlk.ID()) 157 require.NoError(err) 158 159 // Force this node to be the only validator, so to guarantee 160 // it'd be picked if block build time was before MaxVerifyDelay 161 valState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { 162 // a validator with a weight large enough to fully fill the proposers list 163 weight := uint64(proposer.MaxBuildWindows * 2) 164 165 return map[ids.NodeID]*validators.GetValidatorOutput{ 166 proVM.ctx.NodeID: { 167 NodeID: proVM.ctx.NodeID, 168 Weight: weight, 169 }, 170 }, nil 171 } 172 173 coreChildBlk := snowmantest.BuildChild(coreParentBlk) 174 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 175 return coreChildBlk, nil 176 } 177 178 { 179 // Set local clock before MaxVerifyDelay from parent timestamp. 180 // Check that child block is signed. 181 localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay - time.Second) 182 proVM.Set(localTime) 183 184 childBlkIntf, err := proVM.BuildBlock(ctx) 185 require.NoError(err) 186 require.IsType(&postForkBlock{}, childBlkIntf) 187 188 childBlk := childBlkIntf.(*postForkBlock) 189 require.Equal(proVM.ctx.NodeID, childBlk.Proposer()) // signed block 190 } 191 192 { 193 // Set local clock exactly MaxVerifyDelay from parent timestamp. 194 // Check that child block is unsigned. 195 localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay) 196 proVM.Set(localTime) 197 198 childBlkIntf, err := proVM.BuildBlock(ctx) 199 require.NoError(err) 200 require.IsType(&postForkBlock{}, childBlkIntf) 201 202 childBlk := childBlkIntf.(*postForkBlock) 203 require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block 204 } 205 206 { 207 // Set local clock between MaxVerifyDelay and MaxBuildDelay from parent 208 // timestamp. 209 // Check that child block is unsigned. 210 localTime := parentBlk.Timestamp().Add((proposer.MaxVerifyDelay + proposer.MaxBuildDelay) / 2) 211 proVM.Set(localTime) 212 213 childBlkIntf, err := proVM.BuildBlock(ctx) 214 require.NoError(err) 215 require.IsType(&postForkBlock{}, childBlkIntf) 216 217 childBlk := childBlkIntf.(*postForkBlock) 218 require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block 219 } 220 221 { 222 // Set local clock after MaxBuildDelay from parent timestamp. 223 // Check that child block is unsigned. 224 localTime := parentBlk.Timestamp().Add(proposer.MaxBuildDelay) 225 proVM.Set(localTime) 226 227 childBlkIntf, err := proVM.BuildBlock(ctx) 228 require.NoError(err) 229 require.IsType(&postForkBlock{}, childBlkIntf) 230 231 childBlk := childBlkIntf.(*postForkBlock) 232 require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block 233 } 234 } 235 236 func TestPreDurangoNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { 237 require := require.New(t) 238 ctx := context.Background() 239 240 var ( 241 activationTime = time.Unix(0, 0) 242 durangoTime = mockable.MaxTime 243 ) 244 coreVM, valState, proVM, _ := initTestProposerVM(t, activationTime, durangoTime, 0) 245 defer func() { 246 require.NoError(proVM.Shutdown(ctx)) 247 }() 248 249 // Build a post fork block. It'll be the parent block in our test cases 250 parentTime := time.Now().Truncate(time.Second) 251 proVM.Set(parentTime) 252 253 coreParentBlk := snowmantest.BuildChild(snowmantest.Genesis) 254 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 255 return coreParentBlk, nil 256 } 257 coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { 258 switch blkID { 259 case coreParentBlk.ID(): 260 return coreParentBlk, nil 261 case snowmantest.GenesisID: 262 return snowmantest.Genesis, nil 263 default: 264 return nil, errUnknownBlock 265 } 266 } 267 coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { // needed when setting preference 268 switch { 269 case bytes.Equal(b, coreParentBlk.Bytes()): 270 return coreParentBlk, nil 271 case bytes.Equal(b, snowmantest.GenesisBytes): 272 return snowmantest.Genesis, nil 273 default: 274 return nil, errUnknownBlock 275 } 276 } 277 278 parentBlk, err := proVM.BuildBlock(ctx) 279 require.NoError(err) 280 require.NoError(parentBlk.Verify(ctx)) 281 require.NoError(parentBlk.Accept(ctx)) 282 283 // Make sure preference is duly set 284 require.NoError(proVM.SetPreference(ctx, parentBlk.ID())) 285 require.Equal(proVM.preferred, parentBlk.ID()) 286 _, err = proVM.getPostForkBlock(ctx, parentBlk.ID()) 287 require.NoError(err) 288 289 // Mark node as non validator 290 valState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { 291 var ( 292 aValidator = ids.GenerateTestNodeID() 293 294 // a validator with a weight large enough to fully fill the proposers list 295 weight = uint64(proposer.MaxBuildWindows * 2) 296 ) 297 return map[ids.NodeID]*validators.GetValidatorOutput{ 298 aValidator: { 299 NodeID: aValidator, 300 Weight: weight, 301 }, 302 }, nil 303 } 304 305 coreChildBlk := snowmantest.BuildChild(coreParentBlk) 306 coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { 307 return coreChildBlk, nil 308 } 309 310 { 311 // Set local clock before MaxVerifyDelay from parent timestamp. 312 // Check that child block is not built. 313 localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay - time.Second) 314 proVM.Set(localTime) 315 316 _, err := proVM.BuildBlock(ctx) 317 require.ErrorIs(err, errProposerWindowNotStarted) 318 } 319 320 { 321 // Set local clock exactly MaxVerifyDelay from parent timestamp. 322 // Check that child block is not built. 323 localTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay) 324 proVM.Set(localTime) 325 326 _, err := proVM.BuildBlock(ctx) 327 require.ErrorIs(err, errProposerWindowNotStarted) 328 } 329 330 { 331 // Set local clock among MaxVerifyDelay and MaxBuildDelay from parent timestamp 332 // Check that child block is not built. 333 localTime := parentBlk.Timestamp().Add((proposer.MaxVerifyDelay + proposer.MaxBuildDelay) / 2) 334 proVM.Set(localTime) 335 336 _, err := proVM.BuildBlock(ctx) 337 require.ErrorIs(err, errProposerWindowNotStarted) 338 } 339 340 { 341 // Set local clock after MaxBuildDelay from parent timestamp 342 // Check that child block is built and it is unsigned 343 localTime := parentBlk.Timestamp().Add(proposer.MaxBuildDelay) 344 proVM.Set(localTime) 345 346 childBlkIntf, err := proVM.BuildBlock(ctx) 347 require.NoError(err) 348 require.IsType(&postForkBlock{}, childBlkIntf) 349 350 childBlk := childBlkIntf.(*postForkBlock) 351 require.Equal(ids.EmptyNodeID, childBlk.Proposer()) // unsigned block 352 } 353 } 354 355 // We consider cases where this node is not current proposer (may be scheduled in the next future or not). 356 // We check that scheduler is called nonetheless, to be able to process innerVM block requests 357 func TestPostDurangoBuildChildResetScheduler(t *testing.T) { 358 require := require.New(t) 359 ctrl := gomock.NewController(t) 360 361 var ( 362 thisNodeID = ids.GenerateTestNodeID() 363 selectedProposer = ids.GenerateTestNodeID() 364 pChainHeight uint64 = 1337 365 parentID = ids.GenerateTestID() 366 parentTimestamp = time.Now().Truncate(time.Second) 367 now = parentTimestamp.Add(12 * time.Second) 368 parentHeight uint64 = 1234 369 ) 370 371 innerBlk := snowmantest.NewMockBlock(ctrl) 372 innerBlk.EXPECT().Height().Return(parentHeight + 1).AnyTimes() 373 374 vdrState := validators.NewMockState(ctrl) 375 vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() 376 377 windower := proposer.NewMockWindower(ctrl) 378 windower.EXPECT().ExpectedProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 379 Return(selectedProposer, nil).AnyTimes() // return a proposer different from thisNode, to check whether scheduler is reset 380 381 scheduler := scheduler.NewMockScheduler(ctrl) 382 383 pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 384 require.NoError(err) 385 vm := &VM{ 386 Config: Config{ 387 ActivationTime: time.Unix(0, 0), 388 DurangoTime: time.Unix(0, 0), 389 StakingCertLeaf: &staking.Certificate{}, 390 StakingLeafSigner: pk, 391 Registerer: prometheus.NewRegistry(), 392 }, 393 ChainVM: block.NewMockChainVM(ctrl), 394 ctx: &snow.Context{ 395 NodeID: thisNodeID, 396 ValidatorState: vdrState, 397 Log: logging.NoLog{}, 398 }, 399 Windower: windower, 400 Scheduler: scheduler, 401 proposerBuildSlotGauge: prometheus.NewGauge(prometheus.GaugeOpts{}), 402 } 403 vm.Clock.Set(now) 404 405 blk := &postForkCommonComponents{ 406 innerBlk: innerBlk, 407 vm: vm, 408 } 409 410 delays := []time.Duration{ 411 proposer.MaxLookAheadWindow - time.Minute, 412 proposer.MaxLookAheadWindow, 413 proposer.MaxLookAheadWindow + time.Minute, 414 } 415 416 for _, delay := range delays { 417 windower.EXPECT().MinDelayForProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 418 Return(delay, nil).Times(1) 419 420 // we mock the scheduler setting the exact time we expect it to be reset 421 // to 422 expectedSchedulerTime := parentTimestamp.Add(delay) 423 scheduler.EXPECT().SetBuildBlockTime(expectedSchedulerTime).Times(1) 424 425 _, err = blk.buildChild( 426 context.Background(), 427 parentID, 428 parentTimestamp, 429 pChainHeight-1, 430 ) 431 require.ErrorIs(err, errUnexpectedProposer) 432 } 433 }