github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/block/executor/acceptor_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 executor 5 6 import ( 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 "go.uber.org/mock/gomock" 12 13 "github.com/MetalBlockchain/metalgo/chains/atomic" 14 "github.com/MetalBlockchain/metalgo/database" 15 "github.com/MetalBlockchain/metalgo/ids" 16 "github.com/MetalBlockchain/metalgo/snow" 17 "github.com/MetalBlockchain/metalgo/utils" 18 "github.com/MetalBlockchain/metalgo/utils/logging" 19 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 20 "github.com/MetalBlockchain/metalgo/vms/components/verify" 21 "github.com/MetalBlockchain/metalgo/vms/platformvm/block" 22 "github.com/MetalBlockchain/metalgo/vms/platformvm/metrics" 23 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 24 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 25 "github.com/MetalBlockchain/metalgo/vms/platformvm/validators" 26 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 27 ) 28 29 func TestAcceptorVisitProposalBlock(t *testing.T) { 30 require := require.New(t) 31 ctrl := gomock.NewController(t) 32 33 lastAcceptedID := ids.GenerateTestID() 34 35 blk, err := block.NewApricotProposalBlock( 36 lastAcceptedID, 37 1, 38 &txs.Tx{ 39 Unsigned: &txs.AddDelegatorTx{ 40 // Without the line below, this function will error. 41 DelegationRewardsOwner: &secp256k1fx.OutputOwners{}, 42 }, 43 Creds: []verify.Verifiable{}, 44 }, 45 ) 46 require.NoError(err) 47 48 blkID := blk.ID() 49 50 s := state.NewMockState(ctrl) 51 s.EXPECT().Checksum().Return(ids.Empty).Times(1) 52 53 acceptor := &acceptor{ 54 backend: &backend{ 55 ctx: &snow.Context{ 56 Log: logging.NoLog{}, 57 }, 58 blkIDToState: map[ids.ID]*blockState{ 59 blkID: {}, 60 }, 61 state: s, 62 }, 63 metrics: metrics.Noop, 64 validators: validators.TestManager, 65 } 66 67 require.NoError(acceptor.ApricotProposalBlock(blk)) 68 69 require.Equal(blkID, acceptor.backend.lastAccepted) 70 71 _, exists := acceptor.GetState(blkID) 72 require.False(exists) 73 74 s.EXPECT().GetLastAccepted().Return(lastAcceptedID).Times(1) 75 76 _, exists = acceptor.GetState(lastAcceptedID) 77 require.True(exists) 78 } 79 80 func TestAcceptorVisitAtomicBlock(t *testing.T) { 81 require := require.New(t) 82 ctrl := gomock.NewController(t) 83 84 s := state.NewMockState(ctrl) 85 sharedMemory := atomic.NewMockSharedMemory(ctrl) 86 87 parentID := ids.GenerateTestID() 88 acceptor := &acceptor{ 89 backend: &backend{ 90 lastAccepted: parentID, 91 blkIDToState: make(map[ids.ID]*blockState), 92 state: s, 93 ctx: &snow.Context{ 94 Log: logging.NoLog{}, 95 SharedMemory: sharedMemory, 96 }, 97 }, 98 metrics: metrics.Noop, 99 validators: validators.TestManager, 100 } 101 102 blk, err := block.NewApricotAtomicBlock( 103 parentID, 104 1, 105 &txs.Tx{ 106 Unsigned: &txs.AddDelegatorTx{ 107 // Without the line below, this function will error. 108 DelegationRewardsOwner: &secp256k1fx.OutputOwners{}, 109 }, 110 Creds: []verify.Verifiable{}, 111 }, 112 ) 113 require.NoError(err) 114 115 // Set expected calls on the state. 116 // We should error after [commonAccept] is called. 117 s.EXPECT().SetLastAccepted(blk.ID()).Times(1) 118 s.EXPECT().SetHeight(blk.Height()).Times(1) 119 s.EXPECT().AddStatelessBlock(blk).Times(1) 120 121 err = acceptor.ApricotAtomicBlock(blk) 122 require.ErrorIs(err, errMissingBlockState) 123 124 // Set [blk]'s state in the map as though it had been verified. 125 onAcceptState := state.NewMockDiff(ctrl) 126 childID := ids.GenerateTestID() 127 atomicRequests := make(map[ids.ID]*atomic.Requests) 128 acceptor.backend.blkIDToState[blk.ID()] = &blockState{ 129 onAcceptState: onAcceptState, 130 atomicRequests: atomicRequests, 131 } 132 // Give [blk] a child. 133 childOnAcceptState := state.NewMockDiff(ctrl) 134 childOnAbortState := state.NewMockDiff(ctrl) 135 childOnCommitState := state.NewMockDiff(ctrl) 136 childState := &blockState{ 137 onAcceptState: childOnAcceptState, 138 proposalBlockState: proposalBlockState{ 139 onAbortState: childOnAbortState, 140 onCommitState: childOnCommitState, 141 }, 142 } 143 acceptor.backend.blkIDToState[childID] = childState 144 145 // Set expected calls on dependencies. 146 s.EXPECT().SetLastAccepted(blk.ID()).Times(1) 147 s.EXPECT().SetHeight(blk.Height()).Times(1) 148 s.EXPECT().AddStatelessBlock(blk).Times(1) 149 batch := database.NewMockBatch(ctrl) 150 s.EXPECT().CommitBatch().Return(batch, nil).Times(1) 151 s.EXPECT().Abort().Times(1) 152 onAcceptState.EXPECT().Apply(s).Times(1) 153 sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1) 154 s.EXPECT().Checksum().Return(ids.Empty).Times(1) 155 156 require.NoError(acceptor.ApricotAtomicBlock(blk)) 157 } 158 159 func TestAcceptorVisitStandardBlock(t *testing.T) { 160 require := require.New(t) 161 ctrl := gomock.NewController(t) 162 163 s := state.NewMockState(ctrl) 164 sharedMemory := atomic.NewMockSharedMemory(ctrl) 165 166 parentID := ids.GenerateTestID() 167 clk := &mockable.Clock{} 168 acceptor := &acceptor{ 169 backend: &backend{ 170 lastAccepted: parentID, 171 blkIDToState: make(map[ids.ID]*blockState), 172 state: s, 173 ctx: &snow.Context{ 174 Log: logging.NoLog{}, 175 SharedMemory: sharedMemory, 176 }, 177 }, 178 metrics: metrics.Noop, 179 validators: validators.TestManager, 180 } 181 182 blk, err := block.NewBanffStandardBlock( 183 clk.Time(), 184 parentID, 185 1, 186 []*txs.Tx{ 187 { 188 Unsigned: &txs.AddDelegatorTx{ 189 // Without the line below, this function will error. 190 DelegationRewardsOwner: &secp256k1fx.OutputOwners{}, 191 }, 192 Creds: []verify.Verifiable{}, 193 }, 194 }, 195 ) 196 require.NoError(err) 197 198 // Set expected calls on the state. 199 // We should error after [commonAccept] is called. 200 s.EXPECT().SetLastAccepted(blk.ID()).Times(1) 201 s.EXPECT().SetHeight(blk.Height()).Times(1) 202 s.EXPECT().AddStatelessBlock(blk).Times(1) 203 204 err = acceptor.BanffStandardBlock(blk) 205 require.ErrorIs(err, errMissingBlockState) 206 207 // Set [blk]'s state in the map as though it had been verified. 208 onAcceptState := state.NewMockDiff(ctrl) 209 childID := ids.GenerateTestID() 210 atomicRequests := make(map[ids.ID]*atomic.Requests) 211 calledOnAcceptFunc := false 212 acceptor.backend.blkIDToState[blk.ID()] = &blockState{ 213 onAcceptState: onAcceptState, 214 onAcceptFunc: func() { 215 calledOnAcceptFunc = true 216 }, 217 218 atomicRequests: atomicRequests, 219 } 220 // Give [blk] a child. 221 childOnAcceptState := state.NewMockDiff(ctrl) 222 childOnAbortState := state.NewMockDiff(ctrl) 223 childOnCommitState := state.NewMockDiff(ctrl) 224 childState := &blockState{ 225 onAcceptState: childOnAcceptState, 226 proposalBlockState: proposalBlockState{ 227 onAbortState: childOnAbortState, 228 onCommitState: childOnCommitState, 229 }, 230 } 231 acceptor.backend.blkIDToState[childID] = childState 232 233 // Set expected calls on dependencies. 234 s.EXPECT().SetLastAccepted(blk.ID()).Times(1) 235 s.EXPECT().SetHeight(blk.Height()).Times(1) 236 s.EXPECT().AddStatelessBlock(blk).Times(1) 237 batch := database.NewMockBatch(ctrl) 238 s.EXPECT().CommitBatch().Return(batch, nil).Times(1) 239 s.EXPECT().Abort().Times(1) 240 onAcceptState.EXPECT().Apply(s).Times(1) 241 sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1) 242 s.EXPECT().Checksum().Return(ids.Empty).Times(1) 243 244 require.NoError(acceptor.BanffStandardBlock(blk)) 245 require.True(calledOnAcceptFunc) 246 require.Equal(blk.ID(), acceptor.backend.lastAccepted) 247 } 248 249 func TestAcceptorVisitCommitBlock(t *testing.T) { 250 require := require.New(t) 251 ctrl := gomock.NewController(t) 252 253 s := state.NewMockState(ctrl) 254 sharedMemory := atomic.NewMockSharedMemory(ctrl) 255 256 parentID := ids.GenerateTestID() 257 acceptor := &acceptor{ 258 backend: &backend{ 259 lastAccepted: parentID, 260 blkIDToState: make(map[ids.ID]*blockState), 261 state: s, 262 ctx: &snow.Context{ 263 Log: logging.NoLog{}, 264 SharedMemory: sharedMemory, 265 }, 266 }, 267 metrics: metrics.Noop, 268 validators: validators.TestManager, 269 bootstrapped: &utils.Atomic[bool]{}, 270 } 271 272 blk, err := block.NewApricotCommitBlock(parentID, 1 /*height*/) 273 require.NoError(err) 274 275 err = acceptor.ApricotCommitBlock(blk) 276 require.ErrorIs(err, state.ErrMissingParentState) 277 278 // Set [blk]'s parent in the state map. 279 parentOnAcceptState := state.NewMockDiff(ctrl) 280 parentOnAbortState := state.NewMockDiff(ctrl) 281 parentOnCommitState := state.NewMockDiff(ctrl) 282 parentStatelessBlk := block.NewMockBlock(ctrl) 283 calledOnAcceptFunc := false 284 atomicRequests := make(map[ids.ID]*atomic.Requests) 285 parentState := &blockState{ 286 proposalBlockState: proposalBlockState{ 287 onAbortState: parentOnAbortState, 288 onCommitState: parentOnCommitState, 289 }, 290 statelessBlock: parentStatelessBlk, 291 292 onAcceptState: parentOnAcceptState, 293 onAcceptFunc: func() { 294 calledOnAcceptFunc = true 295 }, 296 297 atomicRequests: atomicRequests, 298 } 299 acceptor.backend.blkIDToState[parentID] = parentState 300 301 blkID := blk.ID() 302 // Set expected calls on dependencies. 303 // Make sure the parent is accepted first. 304 gomock.InOrder( 305 parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1), 306 s.EXPECT().SetLastAccepted(parentID).Times(1), 307 parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1), 308 s.EXPECT().SetHeight(blk.Height()-1).Times(1), 309 s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1), 310 311 s.EXPECT().SetLastAccepted(blkID).Times(1), 312 s.EXPECT().SetHeight(blk.Height()).Times(1), 313 s.EXPECT().AddStatelessBlock(blk).Times(1), 314 ) 315 316 err = acceptor.ApricotCommitBlock(blk) 317 require.ErrorIs(err, errMissingBlockState) 318 319 parentOnCommitState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) 320 321 // Set [blk]'s state in the map as though it had been verified. 322 acceptor.backend.blkIDToState[parentID] = parentState 323 acceptor.backend.blkIDToState[blkID] = &blockState{ 324 onAcceptState: parentState.onCommitState, 325 onAcceptFunc: parentState.onAcceptFunc, 326 327 inputs: parentState.inputs, 328 timestamp: parentOnCommitState.GetTimestamp(), 329 atomicRequests: parentState.atomicRequests, 330 } 331 332 batch := database.NewMockBatch(ctrl) 333 334 // Set expected calls on dependencies. 335 // Make sure the parent is accepted first. 336 gomock.InOrder( 337 parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1), 338 s.EXPECT().SetLastAccepted(parentID).Times(1), 339 parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1), 340 s.EXPECT().SetHeight(blk.Height()-1).Times(1), 341 s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1), 342 343 s.EXPECT().SetLastAccepted(blkID).Times(1), 344 s.EXPECT().SetHeight(blk.Height()).Times(1), 345 s.EXPECT().AddStatelessBlock(blk).Times(1), 346 347 parentOnCommitState.EXPECT().Apply(s).Times(1), 348 s.EXPECT().CommitBatch().Return(batch, nil).Times(1), 349 sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1), 350 s.EXPECT().Checksum().Return(ids.Empty).Times(1), 351 s.EXPECT().Abort().Times(1), 352 ) 353 354 require.NoError(acceptor.ApricotCommitBlock(blk)) 355 require.True(calledOnAcceptFunc) 356 require.Equal(blk.ID(), acceptor.backend.lastAccepted) 357 } 358 359 func TestAcceptorVisitAbortBlock(t *testing.T) { 360 require := require.New(t) 361 ctrl := gomock.NewController(t) 362 363 s := state.NewMockState(ctrl) 364 sharedMemory := atomic.NewMockSharedMemory(ctrl) 365 366 parentID := ids.GenerateTestID() 367 acceptor := &acceptor{ 368 backend: &backend{ 369 lastAccepted: parentID, 370 blkIDToState: make(map[ids.ID]*blockState), 371 state: s, 372 ctx: &snow.Context{ 373 Log: logging.NoLog{}, 374 SharedMemory: sharedMemory, 375 }, 376 }, 377 metrics: metrics.Noop, 378 validators: validators.TestManager, 379 bootstrapped: &utils.Atomic[bool]{}, 380 } 381 382 blk, err := block.NewApricotAbortBlock(parentID, 1 /*height*/) 383 require.NoError(err) 384 385 err = acceptor.ApricotAbortBlock(blk) 386 require.ErrorIs(err, state.ErrMissingParentState) 387 388 // Set [blk]'s parent in the state map. 389 parentOnAcceptState := state.NewMockDiff(ctrl) 390 parentOnAbortState := state.NewMockDiff(ctrl) 391 parentOnCommitState := state.NewMockDiff(ctrl) 392 parentStatelessBlk := block.NewMockBlock(ctrl) 393 calledOnAcceptFunc := false 394 atomicRequests := make(map[ids.ID]*atomic.Requests) 395 parentState := &blockState{ 396 proposalBlockState: proposalBlockState{ 397 onAbortState: parentOnAbortState, 398 onCommitState: parentOnCommitState, 399 }, 400 statelessBlock: parentStatelessBlk, 401 402 onAcceptState: parentOnAcceptState, 403 onAcceptFunc: func() { 404 calledOnAcceptFunc = true 405 }, 406 407 atomicRequests: atomicRequests, 408 } 409 acceptor.backend.blkIDToState[parentID] = parentState 410 411 blkID := blk.ID() 412 // Set expected calls on dependencies. 413 // Make sure the parent is accepted first. 414 gomock.InOrder( 415 parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1), 416 s.EXPECT().SetLastAccepted(parentID).Times(1), 417 parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1), 418 s.EXPECT().SetHeight(blk.Height()-1).Times(1), 419 s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1), 420 421 s.EXPECT().SetLastAccepted(blkID).Times(1), 422 s.EXPECT().SetHeight(blk.Height()).Times(1), 423 s.EXPECT().AddStatelessBlock(blk).Times(1), 424 ) 425 426 err = acceptor.ApricotAbortBlock(blk) 427 require.ErrorIs(err, errMissingBlockState) 428 429 parentOnAbortState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) 430 431 // Set [blk]'s state in the map as though it had been verified. 432 acceptor.backend.blkIDToState[parentID] = parentState 433 acceptor.backend.blkIDToState[blkID] = &blockState{ 434 onAcceptState: parentState.onAbortState, 435 onAcceptFunc: parentState.onAcceptFunc, 436 437 inputs: parentState.inputs, 438 timestamp: parentOnAbortState.GetTimestamp(), 439 atomicRequests: parentState.atomicRequests, 440 } 441 442 batch := database.NewMockBatch(ctrl) 443 444 // Set expected calls on dependencies. 445 // Make sure the parent is accepted first. 446 gomock.InOrder( 447 parentStatelessBlk.EXPECT().ID().Return(parentID).Times(1), 448 s.EXPECT().SetLastAccepted(parentID).Times(1), 449 parentStatelessBlk.EXPECT().Height().Return(blk.Height()-1).Times(1), 450 s.EXPECT().SetHeight(blk.Height()-1).Times(1), 451 s.EXPECT().AddStatelessBlock(parentState.statelessBlock).Times(1), 452 453 s.EXPECT().SetLastAccepted(blkID).Times(1), 454 s.EXPECT().SetHeight(blk.Height()).Times(1), 455 s.EXPECT().AddStatelessBlock(blk).Times(1), 456 457 parentOnAbortState.EXPECT().Apply(s).Times(1), 458 s.EXPECT().CommitBatch().Return(batch, nil).Times(1), 459 sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1), 460 s.EXPECT().Checksum().Return(ids.Empty).Times(1), 461 s.EXPECT().Abort().Times(1), 462 ) 463 464 require.NoError(acceptor.ApricotAbortBlock(blk)) 465 require.True(calledOnAcceptFunc) 466 require.Equal(blk.ID(), acceptor.backend.lastAccepted) 467 }