github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/executor/verifier.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 "errors" 8 "fmt" 9 10 "github.com/ava-labs/avalanchego/chains/atomic" 11 "github.com/ava-labs/avalanchego/ids" 12 "github.com/ava-labs/avalanchego/utils/set" 13 "github.com/ava-labs/avalanchego/vms/components/gas" 14 "github.com/ava-labs/avalanchego/vms/platformvm/block" 15 "github.com/ava-labs/avalanchego/vms/platformvm/state" 16 "github.com/ava-labs/avalanchego/vms/platformvm/status" 17 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 18 "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" 19 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 20 ) 21 22 var ( 23 _ block.Visitor = (*verifier)(nil) 24 25 ErrConflictingBlockTxs = errors.New("block contains conflicting transactions") 26 27 errApricotBlockIssuedAfterFork = errors.New("apricot block issued after fork") 28 errBanffStandardBlockWithoutChanges = errors.New("BanffStandardBlock performs no state changes") 29 errIncorrectBlockHeight = errors.New("incorrect block height") 30 errChildBlockEarlierThanParent = errors.New("proposed timestamp before current chain time") 31 errOptionBlockTimestampNotMatchingParent = errors.New("option block proposed timestamp not matching parent block one") 32 ) 33 34 // verifier handles the logic for verifying a block. 35 type verifier struct { 36 *backend 37 txExecutorBackend *executor.Backend 38 pChainHeight uint64 39 } 40 41 func (v *verifier) BanffAbortBlock(b *block.BanffAbortBlock) error { 42 if err := v.banffOptionBlock(b); err != nil { 43 return err 44 } 45 return v.abortBlock(b) 46 } 47 48 func (v *verifier) BanffCommitBlock(b *block.BanffCommitBlock) error { 49 if err := v.banffOptionBlock(b); err != nil { 50 return err 51 } 52 return v.commitBlock(b) 53 } 54 55 func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { 56 if err := v.banffNonOptionBlock(b); err != nil { 57 return err 58 } 59 60 parentID := b.Parent() 61 onDecisionState, err := state.NewDiff(parentID, v.backend) 62 if err != nil { 63 return err 64 } 65 66 // Advance the time to [nextChainTime]. 67 nextChainTime := b.Timestamp() 68 if _, err := executor.AdvanceTimeTo(v.txExecutorBackend, onDecisionState, nextChainTime); err != nil { 69 return err 70 } 71 72 feeCalculator := state.PickFeeCalculator(v.txExecutorBackend.Config, onDecisionState) 73 inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs( 74 b.Transactions, 75 feeCalculator, 76 onDecisionState, 77 b.Parent(), 78 ) 79 if err != nil { 80 return err 81 } 82 83 onCommitState, err := state.NewDiffOn(onDecisionState) 84 if err != nil { 85 return err 86 } 87 88 onAbortState, err := state.NewDiffOn(onDecisionState) 89 if err != nil { 90 return err 91 } 92 93 return v.proposalBlock( 94 &b.ApricotProposalBlock, 95 onDecisionState, 96 onCommitState, 97 onAbortState, 98 feeCalculator, 99 inputs, 100 atomicRequests, 101 onAcceptFunc, 102 ) 103 } 104 105 func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error { 106 if err := v.banffNonOptionBlock(b); err != nil { 107 return err 108 } 109 110 parentID := b.Parent() 111 onAcceptState, err := state.NewDiff(parentID, v.backend) 112 if err != nil { 113 return err 114 } 115 116 // Advance the time to [b.Timestamp()]. 117 changed, err := executor.AdvanceTimeTo( 118 v.txExecutorBackend, 119 onAcceptState, 120 b.Timestamp(), 121 ) 122 if err != nil { 123 return err 124 } 125 126 // If this block doesn't perform any changes, then it should never have been 127 // issued. 128 if !changed && len(b.Transactions) == 0 { 129 return errBanffStandardBlockWithoutChanges 130 } 131 132 feeCalculator := state.PickFeeCalculator(v.txExecutorBackend.Config, onAcceptState) 133 return v.standardBlock(&b.ApricotStandardBlock, feeCalculator, onAcceptState) 134 } 135 136 func (v *verifier) ApricotAbortBlock(b *block.ApricotAbortBlock) error { 137 if err := v.apricotCommonBlock(b); err != nil { 138 return err 139 } 140 return v.abortBlock(b) 141 } 142 143 func (v *verifier) ApricotCommitBlock(b *block.ApricotCommitBlock) error { 144 if err := v.apricotCommonBlock(b); err != nil { 145 return err 146 } 147 return v.commitBlock(b) 148 } 149 150 func (v *verifier) ApricotProposalBlock(b *block.ApricotProposalBlock) error { 151 if err := v.apricotCommonBlock(b); err != nil { 152 return err 153 } 154 155 parentID := b.Parent() 156 onCommitState, err := state.NewDiff(parentID, v.backend) 157 if err != nil { 158 return err 159 } 160 onAbortState, err := state.NewDiff(parentID, v.backend) 161 if err != nil { 162 return err 163 } 164 165 var ( 166 timestamp = onCommitState.GetTimestamp() // Equal to parent timestamp 167 feeCalculator = state.NewStaticFeeCalculator(v.txExecutorBackend.Config, timestamp) 168 ) 169 return v.proposalBlock(b, nil, onCommitState, onAbortState, feeCalculator, nil, nil, nil) 170 } 171 172 func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error { 173 if err := v.apricotCommonBlock(b); err != nil { 174 return err 175 } 176 177 parentID := b.Parent() 178 onAcceptState, err := state.NewDiff(parentID, v) 179 if err != nil { 180 return err 181 } 182 183 var ( 184 timestamp = onAcceptState.GetTimestamp() // Equal to parent timestamp 185 feeCalculator = state.NewStaticFeeCalculator(v.txExecutorBackend.Config, timestamp) 186 ) 187 return v.standardBlock(b, feeCalculator, onAcceptState) 188 } 189 190 func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error { 191 // We call [commonBlock] here rather than [apricotCommonBlock] because below 192 // this check we perform the more strict check that ApricotPhase5 isn't 193 // activated. 194 if err := v.commonBlock(b); err != nil { 195 return err 196 } 197 198 parentID := b.Parent() 199 currentTimestamp := v.getTimestamp(parentID) 200 cfg := v.txExecutorBackend.Config 201 if cfg.UpgradeConfig.IsApricotPhase5Activated(currentTimestamp) { 202 return fmt.Errorf( 203 "the chain timestamp (%d) is after the apricot phase 5 time (%d), hence atomic transactions should go through the standard block", 204 currentTimestamp.Unix(), 205 cfg.UpgradeConfig.ApricotPhase5Time.Unix(), 206 ) 207 } 208 209 feeCalculator := state.NewStaticFeeCalculator(v.txExecutorBackend.Config, currentTimestamp) 210 atomicExecutor := executor.AtomicTxExecutor{ 211 Backend: v.txExecutorBackend, 212 FeeCalculator: feeCalculator, 213 ParentID: parentID, 214 StateVersions: v, 215 Tx: b.Tx, 216 } 217 218 if err := b.Tx.Unsigned.Visit(&atomicExecutor); err != nil { 219 txID := b.Tx.ID() 220 v.MarkDropped(txID, err) // cache tx as dropped 221 return fmt.Errorf("tx %s failed semantic verification: %w", txID, err) 222 } 223 224 atomicExecutor.OnAccept.AddTx(b.Tx, status.Committed) 225 226 if err := v.verifyUniqueInputs(parentID, atomicExecutor.Inputs); err != nil { 227 return err 228 } 229 230 v.Mempool.Remove(b.Tx) 231 232 blkID := b.ID() 233 v.blkIDToState[blkID] = &blockState{ 234 statelessBlock: b, 235 236 onAcceptState: atomicExecutor.OnAccept, 237 238 inputs: atomicExecutor.Inputs, 239 timestamp: atomicExecutor.OnAccept.GetTimestamp(), 240 atomicRequests: atomicExecutor.AtomicRequests, 241 verifiedHeights: set.Of(v.pChainHeight), 242 } 243 return nil 244 } 245 246 func (v *verifier) banffOptionBlock(b block.BanffBlock) error { 247 if err := v.commonBlock(b); err != nil { 248 return err 249 } 250 251 // Banff option blocks must be uniquely generated from the 252 // BanffProposalBlock. This means that the timestamp must be 253 // standardized to a specific value. Therefore, we require the timestamp to 254 // be equal to the parents timestamp. 255 parentID := b.Parent() 256 parentBlkTime := v.getTimestamp(parentID) 257 blkTime := b.Timestamp() 258 if !blkTime.Equal(parentBlkTime) { 259 return fmt.Errorf( 260 "%w parent block timestamp (%s) option block timestamp (%s)", 261 errOptionBlockTimestampNotMatchingParent, 262 parentBlkTime, 263 blkTime, 264 ) 265 } 266 return nil 267 } 268 269 func (v *verifier) banffNonOptionBlock(b block.BanffBlock) error { 270 if err := v.commonBlock(b); err != nil { 271 return err 272 } 273 274 parentID := b.Parent() 275 parentState, ok := v.GetState(parentID) 276 if !ok { 277 return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) 278 } 279 280 newChainTime := b.Timestamp() 281 parentChainTime := parentState.GetTimestamp() 282 if newChainTime.Before(parentChainTime) { 283 return fmt.Errorf( 284 "%w: proposed timestamp (%s), chain time (%s)", 285 errChildBlockEarlierThanParent, 286 newChainTime, 287 parentChainTime, 288 ) 289 } 290 291 nextStakerChangeTime, err := state.GetNextStakerChangeTime(parentState) 292 if err != nil { 293 return fmt.Errorf("could not verify block timestamp: %w", err) 294 } 295 296 now := v.txExecutorBackend.Clk.Time() 297 return executor.VerifyNewChainTime( 298 newChainTime, 299 nextStakerChangeTime, 300 now, 301 ) 302 } 303 304 func (v *verifier) apricotCommonBlock(b block.Block) error { 305 // We can use the parent timestamp here, because we are guaranteed that the 306 // parent was verified. Apricot blocks only update the timestamp with 307 // AdvanceTimeTxs. This means that this block's timestamp will be equal to 308 // the parent block's timestamp; unless this is a CommitBlock. In order for 309 // the timestamp of the CommitBlock to be after the Banff activation, 310 // the parent ApricotProposalBlock must include an AdvanceTimeTx with a 311 // timestamp after the Banff timestamp. This is verified not to occur 312 // during the verification of the ProposalBlock. 313 parentID := b.Parent() 314 timestamp := v.getTimestamp(parentID) 315 if v.txExecutorBackend.Config.UpgradeConfig.IsBanffActivated(timestamp) { 316 return fmt.Errorf("%w: timestamp = %s", errApricotBlockIssuedAfterFork, timestamp) 317 } 318 return v.commonBlock(b) 319 } 320 321 func (v *verifier) commonBlock(b block.Block) error { 322 parentID := b.Parent() 323 parent, err := v.GetBlock(parentID) 324 if err != nil { 325 return err 326 } 327 328 expectedHeight := parent.Height() + 1 329 height := b.Height() 330 if expectedHeight != height { 331 return fmt.Errorf( 332 "%w expected %d, but found %d", 333 errIncorrectBlockHeight, 334 expectedHeight, 335 height, 336 ) 337 } 338 return nil 339 } 340 341 // abortBlock populates the state of this block if [nil] is returned 342 func (v *verifier) abortBlock(b block.Block) error { 343 parentID := b.Parent() 344 onAbortState, ok := v.getOnAbortState(parentID) 345 if !ok { 346 return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) 347 } 348 349 blkID := b.ID() 350 v.blkIDToState[blkID] = &blockState{ 351 statelessBlock: b, 352 onAcceptState: onAbortState, 353 timestamp: onAbortState.GetTimestamp(), 354 verifiedHeights: set.Of(v.pChainHeight), 355 } 356 return nil 357 } 358 359 // commitBlock populates the state of this block if [nil] is returned 360 func (v *verifier) commitBlock(b block.Block) error { 361 parentID := b.Parent() 362 onCommitState, ok := v.getOnCommitState(parentID) 363 if !ok { 364 return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) 365 } 366 367 blkID := b.ID() 368 v.blkIDToState[blkID] = &blockState{ 369 statelessBlock: b, 370 onAcceptState: onCommitState, 371 timestamp: onCommitState.GetTimestamp(), 372 verifiedHeights: set.Of(v.pChainHeight), 373 } 374 return nil 375 } 376 377 // proposalBlock populates the state of this block if [nil] is returned 378 func (v *verifier) proposalBlock( 379 b *block.ApricotProposalBlock, 380 onDecisionState state.Diff, 381 onCommitState state.Diff, 382 onAbortState state.Diff, 383 feeCalculator fee.Calculator, 384 inputs set.Set[ids.ID], 385 atomicRequests map[ids.ID]*atomic.Requests, 386 onAcceptFunc func(), 387 ) error { 388 txExecutor := executor.ProposalTxExecutor{ 389 OnCommitState: onCommitState, 390 OnAbortState: onAbortState, 391 Backend: v.txExecutorBackend, 392 FeeCalculator: feeCalculator, 393 Tx: b.Tx, 394 } 395 396 if err := b.Tx.Unsigned.Visit(&txExecutor); err != nil { 397 txID := b.Tx.ID() 398 v.MarkDropped(txID, err) // cache tx as dropped 399 return err 400 } 401 402 onCommitState.AddTx(b.Tx, status.Committed) 403 onAbortState.AddTx(b.Tx, status.Aborted) 404 405 v.Mempool.Remove(b.Tx) 406 407 blkID := b.ID() 408 v.blkIDToState[blkID] = &blockState{ 409 proposalBlockState: proposalBlockState{ 410 onDecisionState: onDecisionState, 411 onCommitState: onCommitState, 412 onAbortState: onAbortState, 413 }, 414 415 statelessBlock: b, 416 417 onAcceptFunc: onAcceptFunc, 418 419 inputs: inputs, 420 // It is safe to use [b.onAbortState] here because the timestamp will 421 // never be modified by an Apricot Abort block and the timestamp will 422 // always be the same as the Banff Proposal Block. 423 timestamp: onAbortState.GetTimestamp(), 424 atomicRequests: atomicRequests, 425 verifiedHeights: set.Of(v.pChainHeight), 426 } 427 return nil 428 } 429 430 // standardBlock populates the state of this block if [nil] is returned 431 func (v *verifier) standardBlock( 432 b *block.ApricotStandardBlock, 433 feeCalculator fee.Calculator, 434 onAcceptState state.Diff, 435 ) error { 436 inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, feeCalculator, onAcceptState, b.Parent()) 437 if err != nil { 438 return err 439 } 440 441 v.Mempool.Remove(b.Transactions...) 442 443 blkID := b.ID() 444 v.blkIDToState[blkID] = &blockState{ 445 statelessBlock: b, 446 447 onAcceptState: onAcceptState, 448 onAcceptFunc: onAcceptFunc, 449 450 timestamp: onAcceptState.GetTimestamp(), 451 inputs: inputs, 452 atomicRequests: atomicRequests, 453 verifiedHeights: set.Of(v.pChainHeight), 454 } 455 return nil 456 } 457 458 func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator fee.Calculator, state state.Diff, parentID ids.ID) ( 459 set.Set[ids.ID], 460 map[ids.ID]*atomic.Requests, 461 func(), 462 error, 463 ) { 464 // Complexity is limited first to avoid processing too large of a block. 465 if timestamp := state.GetTimestamp(); v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { 466 var blockComplexity gas.Dimensions 467 for _, tx := range txs { 468 txComplexity, err := fee.TxComplexity(tx.Unsigned) 469 if err != nil { 470 txID := tx.ID() 471 v.MarkDropped(txID, err) 472 return nil, nil, nil, err 473 } 474 475 blockComplexity, err = blockComplexity.Add(&txComplexity) 476 if err != nil { 477 return nil, nil, nil, err 478 } 479 } 480 481 blockGas, err := blockComplexity.ToGas(v.txExecutorBackend.Config.DynamicFeeConfig.Weights) 482 if err != nil { 483 return nil, nil, nil, err 484 } 485 486 // If this block exceeds the available capacity, ConsumeGas will return 487 // an error. 488 feeState := state.GetFeeState() 489 feeState, err = feeState.ConsumeGas(blockGas) 490 if err != nil { 491 return nil, nil, nil, err 492 } 493 494 // Updating the fee state prior to executing the transactions is fine 495 // because the fee calculator was already created. 496 state.SetFeeState(feeState) 497 } 498 499 var ( 500 onAcceptFunc func() 501 inputs set.Set[ids.ID] 502 funcs = make([]func(), 0, len(txs)) 503 atomicRequests = make(map[ids.ID]*atomic.Requests) 504 ) 505 for _, tx := range txs { 506 txExecutor := executor.StandardTxExecutor{ 507 Backend: v.txExecutorBackend, 508 State: state, 509 FeeCalculator: feeCalculator, 510 Tx: tx, 511 } 512 if err := tx.Unsigned.Visit(&txExecutor); err != nil { 513 txID := tx.ID() 514 v.MarkDropped(txID, err) // cache tx as dropped 515 return nil, nil, nil, err 516 } 517 // ensure it doesn't overlap with current input batch 518 if inputs.Overlaps(txExecutor.Inputs) { 519 return nil, nil, nil, ErrConflictingBlockTxs 520 } 521 // Add UTXOs to batch 522 inputs.Union(txExecutor.Inputs) 523 524 state.AddTx(tx, status.Committed) 525 if txExecutor.OnAccept != nil { 526 funcs = append(funcs, txExecutor.OnAccept) 527 } 528 529 for chainID, txRequests := range txExecutor.AtomicRequests { 530 // Add/merge in the atomic requests represented by [tx] 531 chainRequests, exists := atomicRequests[chainID] 532 if !exists { 533 atomicRequests[chainID] = txRequests 534 continue 535 } 536 537 chainRequests.PutRequests = append(chainRequests.PutRequests, txRequests.PutRequests...) 538 chainRequests.RemoveRequests = append(chainRequests.RemoveRequests, txRequests.RemoveRequests...) 539 } 540 } 541 542 if err := v.verifyUniqueInputs(parentID, inputs); err != nil { 543 return nil, nil, nil, err 544 } 545 546 if numFuncs := len(funcs); numFuncs == 1 { 547 onAcceptFunc = funcs[0] 548 } else if numFuncs > 1 { 549 onAcceptFunc = func() { 550 for _, f := range funcs { 551 f() 552 } 553 } 554 } 555 556 return inputs, atomicRequests, onAcceptFunc, nil 557 }