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