github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/builder/builder.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package builder 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "math" 11 "sync" 12 "time" 13 14 "go.uber.org/zap" 15 16 "github.com/ava-labs/avalanchego/ids" 17 "github.com/ava-labs/avalanchego/snow" 18 "github.com/ava-labs/avalanchego/snow/consensus/snowman" 19 "github.com/ava-labs/avalanchego/utils/set" 20 "github.com/ava-labs/avalanchego/utils/timer/mockable" 21 "github.com/ava-labs/avalanchego/utils/units" 22 "github.com/ava-labs/avalanchego/vms/components/gas" 23 "github.com/ava-labs/avalanchego/vms/platformvm/block" 24 "github.com/ava-labs/avalanchego/vms/platformvm/state" 25 "github.com/ava-labs/avalanchego/vms/platformvm/status" 26 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 27 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 28 "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" 29 30 blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" 31 txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" 32 ) 33 34 // targetBlockSize is maximum number of transaction bytes to place into a 35 // StandardBlock 36 const targetBlockSize = 128 * units.KiB 37 38 var ( 39 _ Builder = (*builder)(nil) 40 41 ErrEndOfTime = errors.New("program time is suspiciously far in the future") 42 ErrNoPendingBlocks = errors.New("no pending blocks") 43 errMissingPreferredState = errors.New("missing preferred block state") 44 errCalculatingNextStakerTime = errors.New("failed calculating next staker time") 45 ) 46 47 type Builder interface { 48 mempool.Mempool 49 50 // StartBlockTimer starts to issue block creation requests to advance the 51 // chain timestamp. 52 StartBlockTimer() 53 54 // ResetBlockTimer forces the block timer to recalculate when it should 55 // advance the chain timestamp. 56 ResetBlockTimer() 57 58 // ShutdownBlockTimer stops block creation requests to advance the chain 59 // timestamp. 60 // 61 // Invariant: Assumes the context lock is held when calling. 62 ShutdownBlockTimer() 63 64 // BuildBlock can be called to attempt to create a new block 65 BuildBlock(context.Context) (snowman.Block, error) 66 67 // PackAllBlockTxs returns an array of all txs that could be packed into a 68 // valid block of infinite size. The returned txs are all verified against 69 // the preferred state. 70 // 71 // Note: This function does not call the consensus engine. 72 PackAllBlockTxs() ([]*txs.Tx, error) 73 } 74 75 // builder implements a simple builder to convert txs into valid blocks 76 type builder struct { 77 mempool.Mempool 78 79 txExecutorBackend *txexecutor.Backend 80 blkManager blockexecutor.Manager 81 82 // resetTimer is used to signal that the block builder timer should update 83 // when it will trigger building of a block. 84 resetTimer chan struct{} 85 closed chan struct{} 86 closeOnce sync.Once 87 } 88 89 func New( 90 mempool mempool.Mempool, 91 txExecutorBackend *txexecutor.Backend, 92 blkManager blockexecutor.Manager, 93 ) Builder { 94 return &builder{ 95 Mempool: mempool, 96 txExecutorBackend: txExecutorBackend, 97 blkManager: blkManager, 98 resetTimer: make(chan struct{}, 1), 99 closed: make(chan struct{}), 100 } 101 } 102 103 func (b *builder) StartBlockTimer() { 104 go func() { 105 timer := time.NewTimer(0) 106 defer timer.Stop() 107 108 for { 109 // Invariant: The [timer] is not stopped. 110 select { 111 case <-timer.C: 112 case <-b.resetTimer: 113 if !timer.Stop() { 114 <-timer.C 115 } 116 case <-b.closed: 117 return 118 } 119 120 // Note: Because the context lock is not held here, it is possible 121 // that [ShutdownBlockTimer] is called concurrently with this 122 // execution. 123 for { 124 duration, err := b.durationToSleep() 125 if err != nil { 126 b.txExecutorBackend.Ctx.Log.Error("block builder encountered a fatal error", 127 zap.Error(err), 128 ) 129 return 130 } 131 132 if duration > 0 { 133 timer.Reset(duration) 134 break 135 } 136 137 // Block needs to be issued to advance time. 138 b.Mempool.RequestBuildBlock(true /*=emptyBlockPermitted*/) 139 140 // Invariant: ResetBlockTimer is guaranteed to be called after 141 // [durationToSleep] returns a value <= 0. This is because we 142 // are guaranteed to attempt to build block. After building a 143 // valid block, the chain will have its preference updated which 144 // may change the duration to sleep and trigger a timer reset. 145 select { 146 case <-b.resetTimer: 147 case <-b.closed: 148 return 149 } 150 } 151 } 152 }() 153 } 154 155 func (b *builder) durationToSleep() (time.Duration, error) { 156 // Grabbing the lock here enforces that this function is not called mid-way 157 // through modifying of the state. 158 b.txExecutorBackend.Ctx.Lock.Lock() 159 defer b.txExecutorBackend.Ctx.Lock.Unlock() 160 161 // If [ShutdownBlockTimer] was called, we want to exit the block timer 162 // goroutine. We check this with the context lock held because 163 // [ShutdownBlockTimer] is expected to only be called with the context lock 164 // held. 165 select { 166 case <-b.closed: 167 return 0, nil 168 default: 169 } 170 171 preferredID := b.blkManager.Preferred() 172 preferredState, ok := b.blkManager.GetState(preferredID) 173 if !ok { 174 return 0, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) 175 } 176 177 nextStakerChangeTime, err := state.GetNextStakerChangeTime(preferredState) 178 if err != nil { 179 return 0, fmt.Errorf("%w of %s: %w", errCalculatingNextStakerTime, preferredID, err) 180 } 181 182 now := b.txExecutorBackend.Clk.Time() 183 return nextStakerChangeTime.Sub(now), nil 184 } 185 186 func (b *builder) ResetBlockTimer() { 187 // Ensure that the timer will be reset at least once. 188 select { 189 case b.resetTimer <- struct{}{}: 190 default: 191 } 192 } 193 194 func (b *builder) ShutdownBlockTimer() { 195 b.closeOnce.Do(func() { 196 close(b.closed) 197 }) 198 } 199 200 // BuildBlock builds a block to be added to consensus. 201 // This method removes the transactions from the returned 202 // blocks from the mempool. 203 func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { 204 // If there are still transactions in the mempool, then we need to 205 // re-trigger block building. 206 defer b.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/) 207 208 b.txExecutorBackend.Ctx.Log.Debug("starting to attempt to build a block") 209 210 // Get the block to build on top of and retrieve the new block's context. 211 preferredID := b.blkManager.Preferred() 212 preferred, err := b.blkManager.GetBlock(preferredID) 213 if err != nil { 214 return nil, err 215 } 216 nextHeight := preferred.Height() + 1 217 preferredState, ok := b.blkManager.GetState(preferredID) 218 if !ok { 219 return nil, fmt.Errorf("%w: %s", state.ErrMissingParentState, preferredID) 220 } 221 222 timestamp, timeWasCapped, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk) 223 if err != nil { 224 return nil, fmt.Errorf("could not calculate next staker change time: %w", err) 225 } 226 227 statelessBlk, err := buildBlock( 228 b, 229 preferredID, 230 nextHeight, 231 timestamp, 232 timeWasCapped, 233 preferredState, 234 ) 235 if err != nil { 236 return nil, err 237 } 238 239 return b.blkManager.NewBlock(statelessBlk), nil 240 } 241 242 func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) { 243 preferredID := b.blkManager.Preferred() 244 preferredState, ok := b.blkManager.GetState(preferredID) 245 if !ok { 246 return nil, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) 247 } 248 249 timestamp, _, err := state.NextBlockTime(preferredState, b.txExecutorBackend.Clk) 250 if err != nil { 251 return nil, fmt.Errorf("could not calculate next staker change time: %w", err) 252 } 253 254 if !b.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { 255 return packDurangoBlockTxs( 256 preferredID, 257 preferredState, 258 b.Mempool, 259 b.txExecutorBackend, 260 b.blkManager, 261 timestamp, 262 math.MaxInt, 263 ) 264 } 265 return packEtnaBlockTxs( 266 preferredID, 267 preferredState, 268 b.Mempool, 269 b.txExecutorBackend, 270 b.blkManager, 271 timestamp, 272 math.MaxUint64, 273 ) 274 } 275 276 // [timestamp] is min(max(now, parent timestamp), next staker change time) 277 func buildBlock( 278 builder *builder, 279 parentID ids.ID, 280 height uint64, 281 timestamp time.Time, 282 forceAdvanceTime bool, 283 parentState state.Chain, 284 ) (block.Block, error) { 285 var ( 286 blockTxs []*txs.Tx 287 err error 288 ) 289 if builder.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) { 290 blockTxs, err = packEtnaBlockTxs( 291 parentID, 292 parentState, 293 builder.Mempool, 294 builder.txExecutorBackend, 295 builder.blkManager, 296 timestamp, 297 0, // minCapacity is 0 as we want to honor the capacity in state. 298 ) 299 } else { 300 blockTxs, err = packDurangoBlockTxs( 301 parentID, 302 parentState, 303 builder.Mempool, 304 builder.txExecutorBackend, 305 builder.blkManager, 306 timestamp, 307 targetBlockSize, 308 ) 309 } 310 if err != nil { 311 return nil, fmt.Errorf("failed to pack block txs: %w", err) 312 } 313 314 // Try rewarding stakers whose staking period ends at the new chain time. 315 // This is done first to prioritize advancing the timestamp as quickly as 316 // possible. 317 stakerTxID, shouldReward, err := getNextStakerToReward(timestamp, parentState) 318 if err != nil { 319 return nil, fmt.Errorf("could not find next staker to reward: %w", err) 320 } 321 if shouldReward { 322 rewardValidatorTx, err := NewRewardValidatorTx(builder.txExecutorBackend.Ctx, stakerTxID) 323 if err != nil { 324 return nil, fmt.Errorf("could not build tx to reward staker: %w", err) 325 } 326 327 return block.NewBanffProposalBlock( 328 timestamp, 329 parentID, 330 height, 331 rewardValidatorTx, 332 blockTxs, 333 ) 334 } 335 336 // If there is no reason to build a block, don't. 337 if len(blockTxs) == 0 && !forceAdvanceTime { 338 builder.txExecutorBackend.Ctx.Log.Debug("no pending txs to issue into a block") 339 return nil, ErrNoPendingBlocks 340 } 341 342 // Issue a block with as many transactions as possible. 343 return block.NewBanffStandardBlock( 344 timestamp, 345 parentID, 346 height, 347 blockTxs, 348 ) 349 } 350 351 func packDurangoBlockTxs( 352 parentID ids.ID, 353 parentState state.Chain, 354 mempool mempool.Mempool, 355 backend *txexecutor.Backend, 356 manager blockexecutor.Manager, 357 timestamp time.Time, 358 remainingSize int, 359 ) ([]*txs.Tx, error) { 360 stateDiff, err := state.NewDiffOn(parentState) 361 if err != nil { 362 return nil, err 363 } 364 365 if _, err := txexecutor.AdvanceTimeTo(backend, stateDiff, timestamp); err != nil { 366 return nil, err 367 } 368 369 var ( 370 blockTxs []*txs.Tx 371 inputs set.Set[ids.ID] 372 feeCalculator = state.PickFeeCalculator(backend.Config, stateDiff) 373 ) 374 for { 375 tx, exists := mempool.Peek() 376 if !exists { 377 break 378 } 379 txSize := len(tx.Bytes()) 380 if txSize > remainingSize { 381 break 382 } 383 384 shouldAdd, err := executeTx( 385 parentID, 386 stateDiff, 387 mempool, 388 backend, 389 manager, 390 &inputs, 391 feeCalculator, 392 tx, 393 ) 394 if err != nil { 395 return nil, err 396 } 397 if !shouldAdd { 398 continue 399 } 400 401 remainingSize -= txSize 402 blockTxs = append(blockTxs, tx) 403 } 404 405 return blockTxs, nil 406 } 407 408 func packEtnaBlockTxs( 409 parentID ids.ID, 410 parentState state.Chain, 411 mempool mempool.Mempool, 412 backend *txexecutor.Backend, 413 manager blockexecutor.Manager, 414 timestamp time.Time, 415 minCapacity gas.Gas, 416 ) ([]*txs.Tx, error) { 417 stateDiff, err := state.NewDiffOn(parentState) 418 if err != nil { 419 return nil, err 420 } 421 422 if _, err := txexecutor.AdvanceTimeTo(backend, stateDiff, timestamp); err != nil { 423 return nil, err 424 } 425 426 feeState := stateDiff.GetFeeState() 427 capacity := max(feeState.Capacity, minCapacity) 428 429 var ( 430 blockTxs []*txs.Tx 431 inputs set.Set[ids.ID] 432 blockComplexity gas.Dimensions 433 feeCalculator = state.PickFeeCalculator(backend.Config, stateDiff) 434 ) 435 for { 436 tx, exists := mempool.Peek() 437 if !exists { 438 break 439 } 440 441 txComplexity, err := fee.TxComplexity(tx.Unsigned) 442 if err != nil { 443 return nil, err 444 } 445 newBlockComplexity, err := blockComplexity.Add(&txComplexity) 446 if err != nil { 447 return nil, err 448 } 449 newBlockGas, err := newBlockComplexity.ToGas(backend.Config.DynamicFeeConfig.Weights) 450 if err != nil { 451 return nil, err 452 } 453 if newBlockGas > capacity { 454 break 455 } 456 457 shouldAdd, err := executeTx( 458 parentID, 459 stateDiff, 460 mempool, 461 backend, 462 manager, 463 &inputs, 464 feeCalculator, 465 tx, 466 ) 467 if err != nil { 468 return nil, err 469 } 470 if !shouldAdd { 471 continue 472 } 473 474 blockComplexity = newBlockComplexity 475 blockTxs = append(blockTxs, tx) 476 } 477 478 return blockTxs, nil 479 } 480 481 func executeTx( 482 parentID ids.ID, 483 stateDiff state.Diff, 484 mempool mempool.Mempool, 485 backend *txexecutor.Backend, 486 manager blockexecutor.Manager, 487 inputs *set.Set[ids.ID], 488 feeCalculator fee.Calculator, 489 tx *txs.Tx, 490 ) (bool, error) { 491 mempool.Remove(tx) 492 493 // Invariant: [tx] has already been syntactically verified. 494 495 txDiff, err := state.NewDiffOn(stateDiff) 496 if err != nil { 497 return false, err 498 } 499 500 executor := &txexecutor.StandardTxExecutor{ 501 Backend: backend, 502 State: txDiff, 503 FeeCalculator: feeCalculator, 504 Tx: tx, 505 } 506 507 err = tx.Unsigned.Visit(executor) 508 if err != nil { 509 txID := tx.ID() 510 mempool.MarkDropped(txID, err) 511 return false, nil 512 } 513 514 if inputs.Overlaps(executor.Inputs) { 515 txID := tx.ID() 516 mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs) 517 return false, nil 518 } 519 err = manager.VerifyUniqueInputs(parentID, executor.Inputs) 520 if err != nil { 521 txID := tx.ID() 522 mempool.MarkDropped(txID, err) 523 return false, nil 524 } 525 inputs.Union(executor.Inputs) 526 527 txDiff.AddTx(tx, status.Committed) 528 return true, txDiff.Apply(stateDiff) 529 } 530 531 // getNextStakerToReward returns the next staker txID to remove from the staking 532 // set with a RewardValidatorTx rather than an AdvanceTimeTx. [chainTimestamp] 533 // is the timestamp of the chain at the time this validator would be getting 534 // removed and is used to calculate [shouldReward]. 535 // Returns: 536 // - [txID] of the next staker to reward 537 // - [shouldReward] if the txID exists and is ready to be rewarded 538 // - [err] if something bad happened 539 func getNextStakerToReward( 540 chainTimestamp time.Time, 541 preferredState state.Chain, 542 ) (ids.ID, bool, error) { 543 if !chainTimestamp.Before(mockable.MaxTime) { 544 return ids.Empty, false, ErrEndOfTime 545 } 546 547 currentStakerIterator, err := preferredState.GetCurrentStakerIterator() 548 if err != nil { 549 return ids.Empty, false, err 550 } 551 defer currentStakerIterator.Release() 552 553 for currentStakerIterator.Next() { 554 currentStaker := currentStakerIterator.Value() 555 priority := currentStaker.Priority 556 // If the staker is a permissionless staker (not a permissioned subnet 557 // validator), it's the next staker we will want to remove with a 558 // RewardValidatorTx rather than an AdvanceTimeTx. 559 if priority != txs.SubnetPermissionedValidatorCurrentPriority { 560 return currentStaker.TxID, chainTimestamp.Equal(currentStaker.EndTime), nil 561 } 562 } 563 return ids.Empty, false, nil 564 } 565 566 func NewRewardValidatorTx(ctx *snow.Context, txID ids.ID) (*txs.Tx, error) { 567 utx := &txs.RewardValidatorTx{TxID: txID} 568 tx, err := txs.NewSigned(utx, txs.Codec, nil) 569 if err != nil { 570 return nil, err 571 } 572 return tx, tx.SyntacticVerify(ctx) 573 }