github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/proposal_tx_executor.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 "time" 10 11 "github.com/ava-labs/avalanchego/database" 12 "github.com/ava-labs/avalanchego/ids" 13 "github.com/ava-labs/avalanchego/utils/math" 14 "github.com/ava-labs/avalanchego/vms/components/avax" 15 "github.com/ava-labs/avalanchego/vms/components/verify" 16 "github.com/ava-labs/avalanchego/vms/platformvm/reward" 17 "github.com/ava-labs/avalanchego/vms/platformvm/state" 18 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 19 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 20 ) 21 22 const ( 23 // Maximum future start time for staking/delegating 24 MaxFutureStartTime = 24 * 7 * 2 * time.Hour 25 26 // SyncBound is the synchrony bound used for safe decision making 27 SyncBound = 10 * time.Second 28 29 MaxValidatorWeightFactor = 5 30 ) 31 32 var ( 33 _ txs.Visitor = (*ProposalTxExecutor)(nil) 34 35 ErrRemoveStakerTooEarly = errors.New("attempting to remove staker before their end time") 36 ErrRemoveWrongStaker = errors.New("attempting to remove wrong staker") 37 ErrChildBlockNotAfterParent = errors.New("proposed timestamp not after current chain time") 38 ErrInvalidState = errors.New("generated output isn't valid state") 39 ErrShouldBePermissionlessStaker = errors.New("expected permissionless staker") 40 ErrWrongTxType = errors.New("wrong transaction type") 41 ErrInvalidID = errors.New("invalid ID") 42 ErrProposedAddStakerTxAfterBanff = errors.New("staker transaction proposed after Banff") 43 ErrAdvanceTimeTxIssuedAfterBanff = errors.New("AdvanceTimeTx issued after Banff") 44 ) 45 46 type ProposalTxExecutor struct { 47 // inputs, to be filled before visitor methods are called 48 *Backend 49 FeeCalculator fee.Calculator 50 Tx *txs.Tx 51 // [OnCommitState] is the state used for validation. 52 // [OnCommitState] is modified by this struct's methods to 53 // reflect changes made to the state if the proposal is committed. 54 // 55 // Invariant: Both [OnCommitState] and [OnAbortState] represent the same 56 // state when provided to this struct. 57 OnCommitState state.Diff 58 // [OnAbortState] is modified by this struct's methods to 59 // reflect changes made to the state if the proposal is aborted. 60 OnAbortState state.Diff 61 } 62 63 func (*ProposalTxExecutor) CreateChainTx(*txs.CreateChainTx) error { 64 return ErrWrongTxType 65 } 66 67 func (*ProposalTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error { 68 return ErrWrongTxType 69 } 70 71 func (*ProposalTxExecutor) ImportTx(*txs.ImportTx) error { 72 return ErrWrongTxType 73 } 74 75 func (*ProposalTxExecutor) ExportTx(*txs.ExportTx) error { 76 return ErrWrongTxType 77 } 78 79 func (*ProposalTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error { 80 return ErrWrongTxType 81 } 82 83 func (*ProposalTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error { 84 return ErrWrongTxType 85 } 86 87 func (*ProposalTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error { 88 return ErrWrongTxType 89 } 90 91 func (*ProposalTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error { 92 return ErrWrongTxType 93 } 94 95 func (*ProposalTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error { 96 return ErrWrongTxType 97 } 98 99 func (*ProposalTxExecutor) BaseTx(*txs.BaseTx) error { 100 return ErrWrongTxType 101 } 102 103 func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { 104 // AddValidatorTx is a proposal transaction until the Banff fork 105 // activation. Following the activation, AddValidatorTxs must be issued into 106 // StandardBlocks. 107 currentTimestamp := e.OnCommitState.GetTimestamp() 108 if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { 109 return fmt.Errorf( 110 "%w: timestamp (%s) >= Banff fork time (%s)", 111 ErrProposedAddStakerTxAfterBanff, 112 currentTimestamp, 113 e.Config.UpgradeConfig.BanffTime, 114 ) 115 } 116 117 onAbortOuts, err := verifyAddValidatorTx( 118 e.Backend, 119 e.FeeCalculator, 120 e.OnCommitState, 121 e.Tx, 122 tx, 123 ) 124 if err != nil { 125 return err 126 } 127 128 txID := e.Tx.ID() 129 130 // Set up the state if this tx is committed 131 // Consume the UTXOs 132 avax.Consume(e.OnCommitState, tx.Ins) 133 // Produce the UTXOs 134 avax.Produce(e.OnCommitState, txID, tx.Outs) 135 136 newStaker, err := state.NewPendingStaker(txID, tx) 137 if err != nil { 138 return err 139 } 140 141 if err := e.OnCommitState.PutPendingValidator(newStaker); err != nil { 142 return err 143 } 144 145 // Set up the state if this tx is aborted 146 // Consume the UTXOs 147 avax.Consume(e.OnAbortState, tx.Ins) 148 // Produce the UTXOs 149 avax.Produce(e.OnAbortState, txID, onAbortOuts) 150 return nil 151 } 152 153 func (e *ProposalTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error { 154 // AddSubnetValidatorTx is a proposal transaction until the Banff fork 155 // activation. Following the activation, AddSubnetValidatorTxs must be 156 // issued into StandardBlocks. 157 currentTimestamp := e.OnCommitState.GetTimestamp() 158 if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { 159 return fmt.Errorf( 160 "%w: timestamp (%s) >= Banff fork time (%s)", 161 ErrProposedAddStakerTxAfterBanff, 162 currentTimestamp, 163 e.Config.UpgradeConfig.BanffTime, 164 ) 165 } 166 167 if err := verifyAddSubnetValidatorTx( 168 e.Backend, 169 e.FeeCalculator, 170 e.OnCommitState, 171 e.Tx, 172 tx, 173 ); err != nil { 174 return err 175 } 176 177 txID := e.Tx.ID() 178 179 // Set up the state if this tx is committed 180 // Consume the UTXOs 181 avax.Consume(e.OnCommitState, tx.Ins) 182 // Produce the UTXOs 183 avax.Produce(e.OnCommitState, txID, tx.Outs) 184 185 newStaker, err := state.NewPendingStaker(txID, tx) 186 if err != nil { 187 return err 188 } 189 190 if err := e.OnCommitState.PutPendingValidator(newStaker); err != nil { 191 return err 192 } 193 194 // Set up the state if this tx is aborted 195 // Consume the UTXOs 196 avax.Consume(e.OnAbortState, tx.Ins) 197 // Produce the UTXOs 198 avax.Produce(e.OnAbortState, txID, tx.Outs) 199 return nil 200 } 201 202 func (e *ProposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { 203 // AddDelegatorTx is a proposal transaction until the Banff fork 204 // activation. Following the activation, AddDelegatorTxs must be issued into 205 // StandardBlocks. 206 currentTimestamp := e.OnCommitState.GetTimestamp() 207 if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) { 208 return fmt.Errorf( 209 "%w: timestamp (%s) >= Banff fork time (%s)", 210 ErrProposedAddStakerTxAfterBanff, 211 currentTimestamp, 212 e.Config.UpgradeConfig.BanffTime, 213 ) 214 } 215 216 onAbortOuts, err := verifyAddDelegatorTx( 217 e.Backend, 218 e.FeeCalculator, 219 e.OnCommitState, 220 e.Tx, 221 tx, 222 ) 223 if err != nil { 224 return err 225 } 226 227 txID := e.Tx.ID() 228 229 // Set up the state if this tx is committed 230 // Consume the UTXOs 231 avax.Consume(e.OnCommitState, tx.Ins) 232 // Produce the UTXOs 233 avax.Produce(e.OnCommitState, txID, tx.Outs) 234 235 newStaker, err := state.NewPendingStaker(txID, tx) 236 if err != nil { 237 return err 238 } 239 240 e.OnCommitState.PutPendingDelegator(newStaker) 241 242 // Set up the state if this tx is aborted 243 // Consume the UTXOs 244 avax.Consume(e.OnAbortState, tx.Ins) 245 // Produce the UTXOs 246 avax.Produce(e.OnAbortState, txID, onAbortOuts) 247 return nil 248 } 249 250 func (e *ProposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { 251 switch { 252 case tx == nil: 253 return txs.ErrNilTx 254 case len(e.Tx.Creds) != 0: 255 return errWrongNumberOfCredentials 256 } 257 258 // Validate [newChainTime] 259 newChainTime := tx.Timestamp() 260 if e.Config.UpgradeConfig.IsBanffActivated(newChainTime) { 261 return fmt.Errorf( 262 "%w: proposed timestamp (%s) >= Banff fork time (%s)", 263 ErrAdvanceTimeTxIssuedAfterBanff, 264 newChainTime, 265 e.Config.UpgradeConfig.BanffTime, 266 ) 267 } 268 269 parentChainTime := e.OnCommitState.GetTimestamp() 270 if !newChainTime.After(parentChainTime) { 271 return fmt.Errorf( 272 "%w, proposed timestamp (%s), chain time (%s)", 273 ErrChildBlockNotAfterParent, 274 parentChainTime, 275 parentChainTime, 276 ) 277 } 278 279 // Only allow timestamp to move forward as far as the time of next staker 280 // set change time 281 nextStakerChangeTime, err := state.GetNextStakerChangeTime(e.OnCommitState) 282 if err != nil { 283 return err 284 } 285 286 now := e.Clk.Time() 287 if err := VerifyNewChainTime( 288 newChainTime, 289 nextStakerChangeTime, 290 now, 291 ); err != nil { 292 return err 293 } 294 295 // Note that state doesn't change if this proposal is aborted 296 _, err = AdvanceTimeTo(e.Backend, e.OnCommitState, newChainTime) 297 return err 298 } 299 300 func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error { 301 switch { 302 case tx == nil: 303 return txs.ErrNilTx 304 case tx.TxID == ids.Empty: 305 return ErrInvalidID 306 case len(e.Tx.Creds) != 0: 307 return errWrongNumberOfCredentials 308 } 309 310 currentStakerIterator, err := e.OnCommitState.GetCurrentStakerIterator() 311 if err != nil { 312 return err 313 } 314 if !currentStakerIterator.Next() { 315 return fmt.Errorf("failed to get next staker to remove: %w", database.ErrNotFound) 316 } 317 stakerToReward := currentStakerIterator.Value() 318 currentStakerIterator.Release() 319 320 if stakerToReward.TxID != tx.TxID { 321 return fmt.Errorf( 322 "%w: %s != %s", 323 ErrRemoveWrongStaker, 324 stakerToReward.TxID, 325 tx.TxID, 326 ) 327 } 328 329 // Verify that the chain's timestamp is the validator's end time 330 currentChainTime := e.OnCommitState.GetTimestamp() 331 if !stakerToReward.EndTime.Equal(currentChainTime) { 332 return fmt.Errorf( 333 "%w: TxID = %s with %s < %s", 334 ErrRemoveStakerTooEarly, 335 tx.TxID, 336 currentChainTime, 337 stakerToReward.EndTime, 338 ) 339 } 340 341 stakerTx, _, err := e.OnCommitState.GetTx(stakerToReward.TxID) 342 if err != nil { 343 return fmt.Errorf("failed to get next removed staker tx: %w", err) 344 } 345 346 // Invariant: A [txs.DelegatorTx] does not also implement the 347 // [txs.ValidatorTx] interface. 348 switch uStakerTx := stakerTx.Unsigned.(type) { 349 case txs.ValidatorTx: 350 if err := e.rewardValidatorTx(uStakerTx, stakerToReward); err != nil { 351 return err 352 } 353 354 // Handle staker lifecycle. 355 e.OnCommitState.DeleteCurrentValidator(stakerToReward) 356 e.OnAbortState.DeleteCurrentValidator(stakerToReward) 357 case txs.DelegatorTx: 358 if err := e.rewardDelegatorTx(uStakerTx, stakerToReward); err != nil { 359 return err 360 } 361 362 // Handle staker lifecycle. 363 e.OnCommitState.DeleteCurrentDelegator(stakerToReward) 364 e.OnAbortState.DeleteCurrentDelegator(stakerToReward) 365 default: 366 // Invariant: Permissioned stakers are removed by the advancement of 367 // time and the current chain timestamp is == this staker's 368 // EndTime. This means only permissionless stakers should be 369 // left in the staker set. 370 return ErrShouldBePermissionlessStaker 371 } 372 373 // If the reward is aborted, then the current supply should be decreased. 374 currentSupply, err := e.OnAbortState.GetCurrentSupply(stakerToReward.SubnetID) 375 if err != nil { 376 return err 377 } 378 newSupply, err := math.Sub(currentSupply, stakerToReward.PotentialReward) 379 if err != nil { 380 return err 381 } 382 e.OnAbortState.SetCurrentSupply(stakerToReward.SubnetID, newSupply) 383 return nil 384 } 385 386 func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, validator *state.Staker) error { 387 var ( 388 txID = validator.TxID 389 stake = uValidatorTx.Stake() 390 outputs = uValidatorTx.Outputs() 391 // Invariant: The staked asset must be equal to the reward asset. 392 stakeAsset = stake[0].Asset 393 ) 394 395 // Refund the stake only when validator is about to leave 396 // the staking set 397 for i, out := range stake { 398 utxo := &avax.UTXO{ 399 UTXOID: avax.UTXOID{ 400 TxID: txID, 401 OutputIndex: uint32(len(outputs) + i), 402 }, 403 Asset: out.Asset, 404 Out: out.Output(), 405 } 406 e.OnCommitState.AddUTXO(utxo) 407 e.OnAbortState.AddUTXO(utxo) 408 } 409 410 utxosOffset := 0 411 412 // Provide the reward here 413 reward := validator.PotentialReward 414 if reward > 0 { 415 validationRewardsOwner := uValidatorTx.ValidationRewardsOwner() 416 outIntf, err := e.Fx.CreateOutput(reward, validationRewardsOwner) 417 if err != nil { 418 return fmt.Errorf("failed to create output: %w", err) 419 } 420 out, ok := outIntf.(verify.State) 421 if !ok { 422 return ErrInvalidState 423 } 424 425 utxo := &avax.UTXO{ 426 UTXOID: avax.UTXOID{ 427 TxID: txID, 428 OutputIndex: uint32(len(outputs) + len(stake)), 429 }, 430 Asset: stakeAsset, 431 Out: out, 432 } 433 e.OnCommitState.AddUTXO(utxo) 434 e.OnCommitState.AddRewardUTXO(txID, utxo) 435 436 utxosOffset++ 437 } 438 439 // Provide the accrued delegatee rewards from successful delegations here. 440 delegateeReward, err := e.OnCommitState.GetDelegateeReward( 441 validator.SubnetID, 442 validator.NodeID, 443 ) 444 if err != nil { 445 return fmt.Errorf("failed to fetch accrued delegatee rewards: %w", err) 446 } 447 448 if delegateeReward == 0 { 449 return nil 450 } 451 452 delegationRewardsOwner := uValidatorTx.DelegationRewardsOwner() 453 outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) 454 if err != nil { 455 return fmt.Errorf("failed to create output: %w", err) 456 } 457 out, ok := outIntf.(verify.State) 458 if !ok { 459 return ErrInvalidState 460 } 461 462 onCommitUtxo := &avax.UTXO{ 463 UTXOID: avax.UTXOID{ 464 TxID: txID, 465 OutputIndex: uint32(len(outputs) + len(stake) + utxosOffset), 466 }, 467 Asset: stakeAsset, 468 Out: out, 469 } 470 e.OnCommitState.AddUTXO(onCommitUtxo) 471 e.OnCommitState.AddRewardUTXO(txID, onCommitUtxo) 472 473 // Note: There is no [offset] if the RewardValidatorTx is 474 // aborted, because the validator reward is not awarded. 475 onAbortUtxo := &avax.UTXO{ 476 UTXOID: avax.UTXOID{ 477 TxID: txID, 478 OutputIndex: uint32(len(outputs) + len(stake)), 479 }, 480 Asset: stakeAsset, 481 Out: out, 482 } 483 e.OnAbortState.AddUTXO(onAbortUtxo) 484 e.OnAbortState.AddRewardUTXO(txID, onAbortUtxo) 485 return nil 486 } 487 488 func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, delegator *state.Staker) error { 489 var ( 490 txID = delegator.TxID 491 stake = uDelegatorTx.Stake() 492 outputs = uDelegatorTx.Outputs() 493 // Invariant: The staked asset must be equal to the reward asset. 494 stakeAsset = stake[0].Asset 495 ) 496 497 // Refund the stake only when delegator is about to leave 498 // the staking set 499 for i, out := range stake { 500 utxo := &avax.UTXO{ 501 UTXOID: avax.UTXOID{ 502 TxID: txID, 503 OutputIndex: uint32(len(outputs) + i), 504 }, 505 Asset: out.Asset, 506 Out: out.Output(), 507 } 508 e.OnCommitState.AddUTXO(utxo) 509 e.OnAbortState.AddUTXO(utxo) 510 } 511 512 // We're (possibly) rewarding a delegator, so we need to fetch 513 // the validator they are delegated to. 514 validator, err := e.OnCommitState.GetCurrentValidator(delegator.SubnetID, delegator.NodeID) 515 if err != nil { 516 return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err) 517 } 518 519 vdrTxIntf, _, err := e.OnCommitState.GetTx(validator.TxID) 520 if err != nil { 521 return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err) 522 } 523 524 // Invariant: Delegators must only be able to reference validator 525 // transactions that implement [txs.ValidatorTx]. All 526 // validator transactions implement this interface except the 527 // AddSubnetValidatorTx. 528 vdrTx, ok := vdrTxIntf.Unsigned.(txs.ValidatorTx) 529 if !ok { 530 return ErrWrongTxType 531 } 532 533 // Calculate split of reward between delegator/delegatee 534 delegateeReward, delegatorReward := reward.Split(delegator.PotentialReward, vdrTx.Shares()) 535 536 utxosOffset := 0 537 538 // Reward the delegator here 539 reward := delegatorReward 540 if reward > 0 { 541 rewardsOwner := uDelegatorTx.RewardsOwner() 542 outIntf, err := e.Fx.CreateOutput(reward, rewardsOwner) 543 if err != nil { 544 return fmt.Errorf("failed to create output: %w", err) 545 } 546 out, ok := outIntf.(verify.State) 547 if !ok { 548 return ErrInvalidState 549 } 550 utxo := &avax.UTXO{ 551 UTXOID: avax.UTXOID{ 552 TxID: txID, 553 OutputIndex: uint32(len(outputs) + len(stake)), 554 }, 555 Asset: stakeAsset, 556 Out: out, 557 } 558 559 e.OnCommitState.AddUTXO(utxo) 560 e.OnCommitState.AddRewardUTXO(txID, utxo) 561 562 utxosOffset++ 563 } 564 565 if delegateeReward == 0 { 566 return nil 567 } 568 569 // Reward the delegatee here 570 if e.Config.UpgradeConfig.IsCortinaActivated(validator.StartTime) { 571 previousDelegateeReward, err := e.OnCommitState.GetDelegateeReward( 572 validator.SubnetID, 573 validator.NodeID, 574 ) 575 if err != nil { 576 return fmt.Errorf("failed to get delegatee reward: %w", err) 577 } 578 579 // Invariant: The rewards calculator can never return a 580 // [potentialReward] that would overflow the 581 // accumulated rewards. 582 newDelegateeReward := previousDelegateeReward + delegateeReward 583 584 // For any validators starting after [CortinaTime], we defer rewarding the 585 // [reward] until their staking period is over. 586 err = e.OnCommitState.SetDelegateeReward( 587 validator.SubnetID, 588 validator.NodeID, 589 newDelegateeReward, 590 ) 591 if err != nil { 592 return fmt.Errorf("failed to update delegatee reward: %w", err) 593 } 594 } else { 595 // For any validators who started prior to [CortinaTime], we issue the 596 // [delegateeReward] immediately. 597 delegationRewardsOwner := vdrTx.DelegationRewardsOwner() 598 outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner) 599 if err != nil { 600 return fmt.Errorf("failed to create output: %w", err) 601 } 602 out, ok := outIntf.(verify.State) 603 if !ok { 604 return ErrInvalidState 605 } 606 utxo := &avax.UTXO{ 607 UTXOID: avax.UTXOID{ 608 TxID: txID, 609 OutputIndex: uint32(len(outputs) + len(stake) + utxosOffset), 610 }, 611 Asset: stakeAsset, 612 Out: out, 613 } 614 615 e.OnCommitState.AddUTXO(utxo) 616 e.OnCommitState.AddRewardUTXO(txID, utxo) 617 } 618 return nil 619 }