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