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