From dfb74b946d11a0322b4642c21fd5143ca15f0d3a Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 6 Dec 2023 11:54:57 +0100
Subject: [PATCH 01/14] introduced stakers cold attributes

---
 .../block/executor/proposal_block_test.go     |  1 +
 vms/platformvm/state/diff.go                  | 25 ++++++++
 vms/platformvm/state/mock_state.go            | 45 ++++++++++++++
 vms/platformvm/state/staker.go                | 22 +++++++
 vms/platformvm/state/state.go                 | 27 ++++++++
 .../txs/executor/proposal_tx_executor.go      | 61 ++++++++-----------
 6 files changed, 146 insertions(+), 35 deletions(-)

diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go
index 77c36c08dec3..addf807e3696 100644
--- a/vms/platformvm/block/executor/proposal_block_test.go
+++ b/vms/platformvm/block/executor/proposal_block_test.go
@@ -96,6 +96,7 @@ func TestApricotProposalBlockTimeVerification(t *testing.T) {
 		StartTime: utx.StartTime(),
 		NextTime:  chainTime,
 		EndTime:   chainTime,
+		Priority:  utx.CurrentPriority(),
 	}).Times(2)
 	currentStakersIt.EXPECT().Release()
 	onParentAccept.EXPECT().GetCurrentStakerIterator().Return(currentStakersIt, nil)
diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go
index d509fa69e0dd..05a758371335 100644
--- a/vms/platformvm/state/diff.go
+++ b/vms/platformvm/state/diff.go
@@ -194,6 +194,31 @@ func (d *diff) GetCurrentStakerIterator() (StakerIterator, error) {
 	return d.currentStakerDiffs.GetStakerIterator(parentIterator), nil
 }
 
+func (d *diff) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
+	stakerTx, _, err := d.GetTx(stakerID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
+	}
+	switch uStakerTx := stakerTx.Unsigned.(type) {
+	case txs.ValidatorTx:
+		return &StakerColdAttributes{
+			Stake:                  uStakerTx.Stake(),
+			Outputs:                uStakerTx.Outputs(),
+			Shares:                 uStakerTx.Shares(),
+			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
+			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
+		}, nil
+	case txs.DelegatorTx:
+		return &StakerColdAttributes{
+			Stake:        uStakerTx.Stake(),
+			Outputs:      uStakerTx.Outputs(),
+			RewardsOwner: uStakerTx.RewardsOwner(),
+		}, nil
+	default:
+		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
+	}
+}
+
 func (d *diff) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
 	// If the validator was modified in this diff, return the modified
 	// validator.
diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go
index 8a3aac7d81f1..beeb99e388f0 100644
--- a/vms/platformvm/state/mock_state.go
+++ b/vms/platformvm/state/mock_state.go
@@ -300,6 +300,21 @@ func (mr *MockChainMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), arg0, arg1)
 }
 
+// GetStakerColdAttributes mocks base method.
+func (m *MockChain) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
+	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
+func (mr *MockChainMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockChain)(nil).GetStakerColdAttributes), arg0)
+}
+
 // GetSubnetOwner mocks base method.
 func (m *MockChain) GetSubnetOwner(arg0 ids.ID) (fx.Owner, error) {
 	m.ctrl.T.Helper()
@@ -762,6 +777,21 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *gom
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), arg0, arg1)
 }
 
+// GetStakerColdAttributes mocks base method.
+func (m *MockDiff) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
+	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
+func (mr *MockDiffMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockDiff)(nil).GetStakerColdAttributes), arg0)
+}
+
 // GetSubnetOwner mocks base method.
 func (m *MockDiff) GetSubnetOwner(arg0 ids.ID) (fx.Owner, error) {
 	m.ctrl.T.Helper()
@@ -1378,6 +1408,21 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), arg0)
 }
 
+// GetStakerColdAttributes mocks base method.
+func (m *MockState) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
+	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
+func (mr *MockStateMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockState)(nil).GetStakerColdAttributes), arg0)
+}
+
 // GetStartTime mocks base method.
 func (m *MockState) GetStartTime(arg0 ids.NodeID, arg1 ids.ID) (time.Time, error) {
 	m.ctrl.T.Helper()
diff --git a/vms/platformvm/state/staker.go b/vms/platformvm/state/staker.go
index 37bc512e36cb..9ce952e9a705 100644
--- a/vms/platformvm/state/staker.go
+++ b/vms/platformvm/state/staker.go
@@ -11,6 +11,8 @@ import (
 
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/utils/crypto/bls"
+	"github.com/ava-labs/avalanchego/vms/components/avax"
+	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
 
@@ -121,3 +123,23 @@ func NewPendingStaker(txID ids.ID, staker txs.Staker) (*Staker, error) {
 		Priority:  staker.PendingPriority(),
 	}, nil
 }
+
+// While Staker object contains a staker's hot attributes, likely to be used pretty often
+// StakerColdAttributes contains a staker's cold attributes, rarely used, mostly when rewarding it.
+// Note that both Staker and StakerAttribute content comes from the stakerTx creating the staker.
+// In state.State we do have StakerMetadata information as well, which contains data about the stakers
+// that are generated during staker's activity (mostly uptimes)
+// TODO: consider moving delegatees reward here, out of StakersMetadata.
+type StakerColdAttributes struct {
+	// common attributes
+	Stake   []*avax.TransferableOutput
+	Outputs []*avax.TransferableOutput
+
+	// validators specific attributes
+	Shares                 uint32
+	ValidationRewardsOwner fx.Owner
+	DelegationRewardsOwner fx.Owner
+
+	// delegators specific attributes
+	RewardsOwner fx.Owner
+}
diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go
index 82ff0107762a..dc9952b92fd8 100644
--- a/vms/platformvm/state/state.go
+++ b/vms/platformvm/state/state.go
@@ -118,6 +118,8 @@ type Chain interface {
 
 	AddChain(createChainTx *txs.Tx)
 
+	GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error)
+
 	GetTx(txID ids.ID) (*txs.Tx, status.Status, error)
 	AddTx(tx *txs.Tx, status status.Status)
 }
@@ -733,6 +735,31 @@ func (s *state) GetCurrentStakerIterator() (StakerIterator, error) {
 	return s.currentStakers.GetStakerIterator(), nil
 }
 
+func (s *state) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
+	stakerTx, _, err := s.GetTx(stakerID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
+	}
+	switch uStakerTx := stakerTx.Unsigned.(type) {
+	case txs.ValidatorTx:
+		return &StakerColdAttributes{
+			Stake:                  uStakerTx.Stake(),
+			Outputs:                uStakerTx.Outputs(),
+			Shares:                 uStakerTx.Shares(),
+			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
+			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
+		}, nil
+	case txs.DelegatorTx:
+		return &StakerColdAttributes{
+			Stake:        uStakerTx.Stake(),
+			Outputs:      uStakerTx.Outputs(),
+			RewardsOwner: uStakerTx.RewardsOwner(),
+		}, nil
+	default:
+		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
+	}
+}
+
 func (s *state) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
 	return s.pendingStakers.GetValidator(subnetID, nodeID)
 }
diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go
index bd329b3f2576..8c96abbf4661 100644
--- a/vms/platformvm/txs/executor/proposal_tx_executor.go
+++ b/vms/platformvm/txs/executor/proposal_tx_executor.go
@@ -363,24 +363,28 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 		return err
 	}
 
-	stakerTx, _, err := e.OnCommitState.GetTx(stakerToReward.TxID)
+	stakerAttributes, err := e.OnCommitState.GetStakerColdAttributes(stakerToReward.TxID)
 	if err != nil {
-		return fmt.Errorf("failed to get next removed staker tx: %w", err)
+		return fmt.Errorf("failed to get attributes for staker %d: %w", stakerToReward.TxID, err)
 	}
 
-	// Invariant: A [txs.DelegatorTx] does not also implement the
-	//            [txs.ValidatorTx] interface.
-	switch uStakerTx := stakerTx.Unsigned.(type) {
-	case txs.ValidatorTx:
-		if err := e.rewardValidatorTx(uStakerTx, stakerToReward); err != nil {
+	switch {
+	case stakerToReward.Priority.IsPermissionedValidator():
+		// Invariant: Permissioned stakers are removed by the advancement of
+		//            time and the current chain timestamp is == this staker's
+		//            EndTime. This means only permissionless stakers should be
+		//            left in the staker set.
+		return ErrShouldBePermissionlessStaker
+	case stakerToReward.Priority.IsCurrentValidator():
+		if err := e.rewardValidatorTx(stakerToReward, stakerAttributes); err != nil {
 			return err
 		}
 
 		// Handle staker lifecycle.
 		e.OnCommitState.DeleteCurrentValidator(stakerToReward)
 		e.OnAbortState.DeleteCurrentValidator(stakerToReward)
-	case txs.DelegatorTx:
-		if err := e.rewardDelegatorTx(uStakerTx, stakerToReward); err != nil {
+	case stakerToReward.Priority.IsCurrentDelegator():
+		if err := e.rewardDelegatorTx(stakerToReward, stakerAttributes); err != nil {
 			return err
 		}
 
@@ -388,11 +392,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 		e.OnCommitState.DeleteCurrentDelegator(stakerToReward)
 		e.OnAbortState.DeleteCurrentDelegator(stakerToReward)
 	default:
-		// Invariant: Permissioned stakers are removed by the advancement of
-		//            time and the current chain timestamp is == this staker's
-		//            EndTime. This means only permissionless stakers should be
-		//            left in the staker set.
-		return ErrShouldBePermissionlessStaker
+		return errors.New("unexpected staker type")
 	}
 
 	// If the reward is aborted, then the current supply should be decreased.
@@ -411,11 +411,11 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 	return err
 }
 
-func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, validator *state.Staker) error {
+func (e *ProposalTxExecutor) rewardValidatorTx(validator *state.Staker, valAttributes *state.StakerColdAttributes) error {
 	var (
 		txID    = validator.TxID
-		stake   = uValidatorTx.Stake()
-		outputs = uValidatorTx.Outputs()
+		stake   = valAttributes.Stake
+		outputs = valAttributes.Outputs
 		// Invariant: The staked asset must be equal to the reward asset.
 		stakeAsset = stake[0].Asset
 	)
@@ -440,7 +440,7 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val
 	// Provide the reward here
 	reward := validator.PotentialReward
 	if reward > 0 {
-		validationRewardsOwner := uValidatorTx.ValidationRewardsOwner()
+		validationRewardsOwner := valAttributes.ValidationRewardsOwner
 		outIntf, err := e.Fx.CreateOutput(reward, validationRewardsOwner)
 		if err != nil {
 			return fmt.Errorf("failed to create output: %w", err)
@@ -477,7 +477,7 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val
 		return nil
 	}
 
-	delegationRewardsOwner := uValidatorTx.DelegationRewardsOwner()
+	delegationRewardsOwner := valAttributes.DelegationRewardsOwner
 	outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner)
 	if err != nil {
 		return fmt.Errorf("failed to create output: %w", err)
@@ -513,11 +513,11 @@ func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, val
 	return nil
 }
 
-func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, delegator *state.Staker) error {
+func (e *ProposalTxExecutor) rewardDelegatorTx(delegator *state.Staker, delAttributes *state.StakerColdAttributes) error {
 	var (
 		txID    = delegator.TxID
-		stake   = uDelegatorTx.Stake()
-		outputs = uDelegatorTx.Outputs()
+		stake   = delAttributes.Stake
+		outputs = delAttributes.Outputs
 		// Invariant: The staked asset must be equal to the reward asset.
 		stakeAsset = stake[0].Asset
 	)
@@ -544,29 +544,20 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del
 		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
 	}
 
-	vdrTxIntf, _, err := e.OnCommitState.GetTx(validator.TxID)
+	valAttributes, err := e.OnCommitState.GetStakerColdAttributes(validator.TxID)
 	if err != nil {
 		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
 	}
 
-	// Invariant: Delegators must only be able to reference validator
-	//            transactions that implement [txs.ValidatorTx]. All
-	//            validator transactions implement this interface except the
-	//            AddSubnetValidatorTx.
-	vdrTx, ok := vdrTxIntf.Unsigned.(txs.ValidatorTx)
-	if !ok {
-		return ErrWrongTxType
-	}
-
 	// Calculate split of reward between delegator/delegatee
-	delegateeReward, delegatorReward := reward.Split(delegator.PotentialReward, vdrTx.Shares())
+	delegateeReward, delegatorReward := reward.Split(delegator.PotentialReward, valAttributes.Shares)
 
 	utxosOffset := 0
 
 	// Reward the delegator here
 	reward := delegatorReward
 	if reward > 0 {
-		rewardsOwner := uDelegatorTx.RewardsOwner()
+		rewardsOwner := delAttributes.RewardsOwner
 		outIntf, err := e.Fx.CreateOutput(reward, rewardsOwner)
 		if err != nil {
 			return fmt.Errorf("failed to create output: %w", err)
@@ -622,7 +613,7 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del
 	} else {
 		// For any validators who started prior to [CortinaTime], we issue the
 		// [delegateeReward] immediately.
-		delegationRewardsOwner := vdrTx.DelegationRewardsOwner()
+		delegationRewardsOwner := valAttributes.DelegationRewardsOwner
 		outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner)
 		if err != nil {
 			return fmt.Errorf("failed to create output: %w", err)

From a1fa20da1bdde118bf848d00ba4c11385fc1e05a Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 6 Dec 2023 12:27:39 +0100
Subject: [PATCH 02/14] some more consolidation

---
 vms/platformvm/service.go      | 56 ++++++----------------------------
 vms/platformvm/service_test.go |  2 +-
 vms/platformvm/state/diff.go   |  9 ++++++
 vms/platformvm/state/staker.go |  2 ++
 vms/platformvm/state/state.go  |  9 ++++++
 vms/platformvm/vm.go           |  2 +-
 6 files changed, 32 insertions(+), 48 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index fe9fa66f7b4c..fdb015cb43dc 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -32,9 +32,7 @@ import (
 	"github.com/ava-labs/avalanchego/utils/set"
 	"github.com/ava-labs/avalanchego/vms/components/avax"
 	"github.com/ava-labs/avalanchego/vms/components/keystore"
-	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
 	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
-	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
 	"github.com/ava-labs/avalanchego/vms/platformvm/state"
 	"github.com/ava-labs/avalanchego/vms/platformvm/status"
@@ -86,16 +84,7 @@ var (
 type Service struct {
 	vm                    *VM
 	addrManager           avax.AddressManager
-	stakerAttributesCache *cache.LRU[ids.ID, *stakerAttributes]
-}
-
-// All attributes are optional and may not be filled for each stakerTx.
-type stakerAttributes struct {
-	shares                 uint32
-	rewardsOwner           fx.Owner
-	validationRewardsOwner fx.Owner
-	delegationRewardsOwner fx.Owner
-	proofOfPossession      *signer.ProofOfPossession
+	stakerAttributesCache *cache.LRU[ids.ID, *state.StakerColdAttributes]
 }
 
 // GetHeight returns the height of the last accepted block
@@ -726,7 +715,7 @@ type GetCurrentValidatorsReply struct {
 	Validators []interface{} `json:"validators"`
 }
 
-func (s *Service) loadStakerTxAttributes(txID ids.ID) (*stakerAttributes, error) {
+func (s *Service) loadStakerTxAttributes(txID ids.ID) (*state.StakerColdAttributes, error) {
 	// Lookup tx from the cache first.
 	attr, found := s.stakerAttributesCache.Get(txID)
 	if found {
@@ -734,36 +723,11 @@ func (s *Service) loadStakerTxAttributes(txID ids.ID) (*stakerAttributes, error)
 	}
 
 	// Tx not available in cache; pull it from disk and populate the cache.
-	tx, _, err := s.vm.state.GetTx(txID)
+	attr, err := s.vm.state.GetStakerColdAttributes(txID)
 	if err != nil {
 		return nil, err
 	}
 
-	switch stakerTx := tx.Unsigned.(type) {
-	case txs.ValidatorTx:
-		var pop *signer.ProofOfPossession
-		if staker, ok := stakerTx.(*txs.AddPermissionlessValidatorTx); ok {
-			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
-				pop = s
-			}
-		}
-
-		attr = &stakerAttributes{
-			shares:                 stakerTx.Shares(),
-			validationRewardsOwner: stakerTx.ValidationRewardsOwner(),
-			delegationRewardsOwner: stakerTx.DelegationRewardsOwner(),
-			proofOfPossession:      pop,
-		}
-
-	case txs.DelegatorTx:
-		attr = &stakerAttributes{
-			rewardsOwner: stakerTx.RewardsOwner(),
-		}
-
-	default:
-		return nil, fmt.Errorf("unexpected staker tx type %T", tx.Unsigned)
-	}
-
 	s.stakerAttributesCache.Put(txID, attr)
 	return attr, nil
 }
@@ -856,7 +820,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
 				return err
 			}
 
-			shares := attr.shares
+			shares := attr.Shares
 			delegationFee := json.Float32(100 * float32(shares) / float32(reward.PercentDenominator))
 
 			uptime, err := s.getAPIUptime(currentStaker)
@@ -869,14 +833,14 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
 				validationRewardOwner *platformapi.Owner
 				delegationRewardOwner *platformapi.Owner
 			)
-			validationOwner, ok := attr.validationRewardsOwner.(*secp256k1fx.OutputOwners)
+			validationOwner, ok := attr.ValidationRewardsOwner.(*secp256k1fx.OutputOwners)
 			if ok {
 				validationRewardOwner, err = s.getAPIOwner(validationOwner)
 				if err != nil {
 					return err
 				}
 			}
-			delegationOwner, ok := attr.delegationRewardsOwner.(*secp256k1fx.OutputOwners)
+			delegationOwner, ok := attr.DelegationRewardsOwner.(*secp256k1fx.OutputOwners)
 			if ok {
 				delegationRewardOwner, err = s.getAPIOwner(delegationOwner)
 				if err != nil {
@@ -894,7 +858,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
 				ValidationRewardOwner:  validationRewardOwner,
 				DelegationRewardOwner:  delegationRewardOwner,
 				DelegationFee:          delegationFee,
-				Signer:                 attr.proofOfPossession,
+				Signer:                 attr.ProofOfPossession,
 			}
 			reply.Validators = append(reply.Validators, vdr)
 
@@ -907,7 +871,7 @@ func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidato
 				if err != nil {
 					return err
 				}
-				owner, ok := attr.rewardsOwner.(*secp256k1fx.OutputOwners)
+				owner, ok := attr.RewardsOwner.(*secp256k1fx.OutputOwners)
 				if ok {
 					rewardOwner, err = s.getAPIOwner(owner)
 					if err != nil {
@@ -1064,7 +1028,7 @@ func (s *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidato
 				return err
 			}
 
-			shares := attr.shares
+			shares := attr.Shares
 			delegationFee := json.Float32(100 * float32(shares) / float32(reward.PercentDenominator))
 
 			connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID)
@@ -1072,7 +1036,7 @@ func (s *Service) GetPendingValidators(_ *http.Request, args *GetPendingValidato
 				Staker:        apiStaker,
 				DelegationFee: delegationFee,
 				Connected:     connected,
-				Signer:        attr.proofOfPossession,
+				Signer:        attr.ProofOfPossession,
 			}
 			reply.Validators = append(reply.Validators, vdr)
 
diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go
index 8e2cc3790fc3..96eda95ff09d 100644
--- a/vms/platformvm/service_test.go
+++ b/vms/platformvm/service_test.go
@@ -83,7 +83,7 @@ func defaultService(t *testing.T) (*Service, *mutableSharedMemory) {
 	return &Service{
 		vm:          vm,
 		addrManager: avax.NewAddressManager(vm.ctx),
-		stakerAttributesCache: &cache.LRU[ids.ID, *stakerAttributes]{
+		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerColdAttributes]{
 			Size: stakerAttributesCacheSize,
 		},
 	}, mutableSharedMemory
diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go
index 05a758371335..bf36113f6ed8 100644
--- a/vms/platformvm/state/diff.go
+++ b/vms/platformvm/state/diff.go
@@ -12,6 +12,7 @@ import (
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/vms/components/avax"
 	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
+	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/status"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
@@ -201,12 +202,20 @@ func (d *diff) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes,
 	}
 	switch uStakerTx := stakerTx.Unsigned.(type) {
 	case txs.ValidatorTx:
+		var pop *signer.ProofOfPossession
+		if staker, ok := uStakerTx.(*txs.AddPermissionlessValidatorTx); ok {
+			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
+				pop = s
+			}
+		}
+
 		return &StakerColdAttributes{
 			Stake:                  uStakerTx.Stake(),
 			Outputs:                uStakerTx.Outputs(),
 			Shares:                 uStakerTx.Shares(),
 			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
 			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
+			ProofOfPossession:      pop,
 		}, nil
 	case txs.DelegatorTx:
 		return &StakerColdAttributes{
diff --git a/vms/platformvm/state/staker.go b/vms/platformvm/state/staker.go
index 9ce952e9a705..6273fcc89e32 100644
--- a/vms/platformvm/state/staker.go
+++ b/vms/platformvm/state/staker.go
@@ -13,6 +13,7 @@ import (
 	"github.com/ava-labs/avalanchego/utils/crypto/bls"
 	"github.com/ava-labs/avalanchego/vms/components/avax"
 	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
+	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
 
@@ -139,6 +140,7 @@ type StakerColdAttributes struct {
 	Shares                 uint32
 	ValidationRewardsOwner fx.Owner
 	DelegationRewardsOwner fx.Owner
+	ProofOfPossession      *signer.ProofOfPossession
 
 	// delegators specific attributes
 	RewardsOwner fx.Owner
diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go
index dc9952b92fd8..7de41c9fa778 100644
--- a/vms/platformvm/state/state.go
+++ b/vms/platformvm/state/state.go
@@ -42,6 +42,7 @@ import (
 	"github.com/ava-labs/avalanchego/vms/platformvm/genesis"
 	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
 	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
+	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/status"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 
@@ -742,12 +743,20 @@ func (s *state) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes,
 	}
 	switch uStakerTx := stakerTx.Unsigned.(type) {
 	case txs.ValidatorTx:
+		var pop *signer.ProofOfPossession
+		if staker, ok := uStakerTx.(*txs.AddPermissionlessValidatorTx); ok {
+			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
+				pop = s
+			}
+		}
+
 		return &StakerColdAttributes{
 			Stake:                  uStakerTx.Stake(),
 			Outputs:                uStakerTx.Outputs(),
 			Shares:                 uStakerTx.Shares(),
 			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
 			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
+			ProofOfPossession:      pop,
 		}, nil
 	case txs.DelegatorTx:
 		return &StakerColdAttributes{
diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go
index d9898b873137..da66ddb06b15 100644
--- a/vms/platformvm/vm.go
+++ b/vms/platformvm/vm.go
@@ -420,7 +420,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
 	service := &Service{
 		vm:          vm,
 		addrManager: avax.NewAddressManager(vm.ctx),
-		stakerAttributesCache: &cache.LRU[ids.ID, *stakerAttributes]{
+		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerColdAttributes]{
 			Size: stakerAttributesCacheSize,
 		},
 	}

From e88b5b0aa34e1c666b38074d93199b785906cf7e Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 6 Dec 2023 13:02:02 +0100
Subject: [PATCH 03/14] some more consolidation

---
 vms/platformvm/service.go            | 20 +++----------
 vms/platformvm/state/diff.go         | 16 ++++++++++
 vms/platformvm/state/mock_state.go   | 45 ++++++++++++++++++++++++++++
 vms/platformvm/state/state.go        | 17 +++++++++++
 vms/platformvm/validators/manager.go | 19 ++----------
 5 files changed, 84 insertions(+), 33 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index fdb015cb43dc..2a9edb381c42 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -1921,17 +1921,12 @@ func (s *Service) GetBlockchainStatus(r *http.Request, args *GetBlockchainStatus
 }
 
 func (s *Service) nodeValidates(blockchainID ids.ID) bool {
-	chainTx, _, err := s.vm.state.GetTx(blockchainID)
+	subnetID, err := s.vm.state.GetChainSubnet(blockchainID)
 	if err != nil {
 		return false
 	}
 
-	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
-	if !ok {
-		return false
-	}
-
-	_, isValidator := s.vm.Validators.GetValidator(chain.SubnetID, s.vm.ctx.NodeID)
+	_, isValidator := s.vm.Validators.GetValidator(subnetID, s.vm.ctx.NodeID)
 	return isValidator
 }
 
@@ -1948,15 +1943,8 @@ func (s *Service) chainExists(ctx context.Context, blockID ids.ID, chainID ids.I
 		}
 	}
 
-	tx, _, err := state.GetTx(chainID)
-	if err == database.ErrNotFound {
-		return false, nil
-	}
-	if err != nil {
-		return false, err
-	}
-	_, ok = tx.Unsigned.(*txs.CreateChainTx)
-	return ok, nil
+	_, err := state.GetChainSubnet(chainID)
+	return err == nil, err
 }
 
 // ValidatedByArgs is the arguments for calling ValidatedBy
diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go
index bf36113f6ed8..fcf35e8513f2 100644
--- a/vms/platformvm/state/diff.go
+++ b/vms/platformvm/state/diff.go
@@ -349,6 +349,22 @@ func (d *diff) AddChain(createChainTx *txs.Tx) {
 	}
 }
 
+func (d *diff) GetChainSubnet(chainID ids.ID) (ids.ID, error) {
+	chainTx, _, err := d.GetTx(chainID)
+	if err != nil {
+		return ids.Empty, fmt.Errorf(
+			"problem retrieving blockchain %q: %w",
+			chainID,
+			err,
+		)
+	}
+	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
+	if !ok {
+		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
+	}
+	return chain.SubnetID, nil
+}
+
 func (d *diff) GetTx(txID ids.ID) (*txs.Tx, status.Status, error) {
 	if tx, exists := d.addedTxs[txID]; exists {
 		return tx.tx, tx.status, nil
diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go
index beeb99e388f0..dceff53e3f5d 100644
--- a/vms/platformvm/state/mock_state.go
+++ b/vms/platformvm/state/mock_state.go
@@ -180,6 +180,21 @@ func (mr *MockChainMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockChain)(nil).DeleteUTXO), arg0)
 }
 
+// GetChainSubnet mocks base method.
+func (m *MockChain) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetChainSubnet", arg0)
+	ret0, _ := ret[0].(ids.ID)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetChainSubnet indicates an expected call of GetChainSubnet.
+func (mr *MockChainMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockChain)(nil).GetChainSubnet), arg0)
+}
+
 // GetCurrentDelegatorIterator mocks base method.
 func (m *MockChain) GetCurrentDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (StakerIterator, error) {
 	m.ctrl.T.Helper()
@@ -657,6 +672,21 @@ func (mr *MockDiffMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockDiff)(nil).DeleteUTXO), arg0)
 }
 
+// GetChainSubnet mocks base method.
+func (m *MockDiff) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetChainSubnet", arg0)
+	ret0, _ := ret[0].(ids.ID)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetChainSubnet indicates an expected call of GetChainSubnet.
+func (mr *MockDiffMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockDiff)(nil).GetChainSubnet), arg0)
+}
+
 // GetCurrentDelegatorIterator mocks base method.
 func (m *MockDiff) GetCurrentDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (StakerIterator, error) {
 	m.ctrl.T.Helper()
@@ -1244,6 +1274,21 @@ func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Ca
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).GetBlockIDAtHeight), arg0)
 }
 
+// GetChainSubnet mocks base method.
+func (m *MockState) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetChainSubnet", arg0)
+	ret0, _ := ret[0].(ids.ID)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetChainSubnet indicates an expected call of GetChainSubnet.
+func (mr *MockStateMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockState)(nil).GetChainSubnet), arg0)
+}
+
 // GetChains mocks base method.
 func (m *MockState) GetChains(arg0 ids.ID) ([]*txs.Tx, error) {
 	m.ctrl.T.Helper()
diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go
index 7de41c9fa778..13cccf385fec 100644
--- a/vms/platformvm/state/state.go
+++ b/vms/platformvm/state/state.go
@@ -118,6 +118,7 @@ type Chain interface {
 	AddSubnetTransformation(transformSubnetTx *txs.Tx)
 
 	AddChain(createChainTx *txs.Tx)
+	GetChainSubnet(chainID ids.ID) (ids.ID, error)
 
 	GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error)
 
@@ -995,6 +996,22 @@ func (s *state) AddChain(createChainTxIntf *txs.Tx) {
 	}
 }
 
+func (s *state) GetChainSubnet(chainID ids.ID) (ids.ID, error) {
+	chainTx, _, err := s.GetTx(chainID)
+	if err != nil {
+		return ids.Empty, fmt.Errorf(
+			"problem retrieving blockchain %q: %w",
+			chainID,
+			err,
+		)
+	}
+	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
+	if !ok {
+		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
+	}
+	return chain.SubnetID, nil
+}
+
 func (s *state) getChainDB(subnetID ids.ID) linkeddb.LinkedDB {
 	if chainDB, cached := s.chainDBCache.Get(subnetID); cached {
 		return chainDB
diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go
index a4c5c87a3040..22c67f6da43c 100644
--- a/vms/platformvm/validators/manager.go
+++ b/vms/platformvm/validators/manager.go
@@ -5,7 +5,6 @@ package validators
 
 import (
 	"context"
-	"fmt"
 	"time"
 
 	"github.com/ava-labs/avalanchego/cache"
@@ -19,8 +18,6 @@ import (
 	"github.com/ava-labs/avalanchego/vms/platformvm/block"
 	"github.com/ava-labs/avalanchego/vms/platformvm/config"
 	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
-	"github.com/ava-labs/avalanchego/vms/platformvm/status"
-	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
 
 const (
@@ -43,7 +40,7 @@ type Manager interface {
 }
 
 type State interface {
-	GetTx(txID ids.ID) (*txs.Tx, status.Status, error)
+	GetChainSubnet(chainID ids.ID) (ids.ID, error)
 
 	GetLastAccepted() ids.ID
 	GetStatelessBlock(blockID ids.ID) (block.Block, error)
@@ -353,19 +350,7 @@ func (m *manager) GetSubnetID(_ context.Context, chainID ids.ID) (ids.ID, error)
 		return constants.PrimaryNetworkID, nil
 	}
 
-	chainTx, _, err := m.state.GetTx(chainID)
-	if err != nil {
-		return ids.Empty, fmt.Errorf(
-			"problem retrieving blockchain %q: %w",
-			chainID,
-			err,
-		)
-	}
-	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
-	if !ok {
-		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
-	}
-	return chain.SubnetID, nil
+	return m.state.GetChainSubnet(chainID)
 }
 
 func (m *manager) OnAcceptedBlockID(blkID ids.ID) {

From 146e4d2ca12dd11469c040d9514ccdc992921b7e Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 6 Dec 2023 13:23:46 +0100
Subject: [PATCH 04/14] removed code duplication

---
 vms/platformvm/state/diff.go          | 46 +-------------------
 vms/platformvm/state/state.go         | 46 +-------------------
 vms/platformvm/state/state_helpers.go | 61 +++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 88 deletions(-)
 create mode 100644 vms/platformvm/state/state_helpers.go

diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go
index fcf35e8513f2..a515de360e1d 100644
--- a/vms/platformvm/state/diff.go
+++ b/vms/platformvm/state/diff.go
@@ -12,7 +12,6 @@ import (
 	"github.com/ava-labs/avalanchego/ids"
 	"github.com/ava-labs/avalanchego/vms/components/avax"
 	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
-	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/status"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
@@ -196,36 +195,7 @@ func (d *diff) GetCurrentStakerIterator() (StakerIterator, error) {
 }
 
 func (d *diff) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
-	stakerTx, _, err := d.GetTx(stakerID)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
-	}
-	switch uStakerTx := stakerTx.Unsigned.(type) {
-	case txs.ValidatorTx:
-		var pop *signer.ProofOfPossession
-		if staker, ok := uStakerTx.(*txs.AddPermissionlessValidatorTx); ok {
-			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
-				pop = s
-			}
-		}
-
-		return &StakerColdAttributes{
-			Stake:                  uStakerTx.Stake(),
-			Outputs:                uStakerTx.Outputs(),
-			Shares:                 uStakerTx.Shares(),
-			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
-			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
-			ProofOfPossession:      pop,
-		}, nil
-	case txs.DelegatorTx:
-		return &StakerColdAttributes{
-			Stake:        uStakerTx.Stake(),
-			Outputs:      uStakerTx.Outputs(),
-			RewardsOwner: uStakerTx.RewardsOwner(),
-		}, nil
-	default:
-		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
-	}
+	return getStakerColdAttributes(d, stakerID)
 }
 
 func (d *diff) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
@@ -350,19 +320,7 @@ func (d *diff) AddChain(createChainTx *txs.Tx) {
 }
 
 func (d *diff) GetChainSubnet(chainID ids.ID) (ids.ID, error) {
-	chainTx, _, err := d.GetTx(chainID)
-	if err != nil {
-		return ids.Empty, fmt.Errorf(
-			"problem retrieving blockchain %q: %w",
-			chainID,
-			err,
-		)
-	}
-	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
-	if !ok {
-		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
-	}
-	return chain.SubnetID, nil
+	return getChainSubnet(d, chainID)
 }
 
 func (d *diff) GetTx(txID ids.ID) (*txs.Tx, status.Status, error) {
diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go
index 13cccf385fec..1811d97c7cc5 100644
--- a/vms/platformvm/state/state.go
+++ b/vms/platformvm/state/state.go
@@ -42,7 +42,6 @@ import (
 	"github.com/ava-labs/avalanchego/vms/platformvm/genesis"
 	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
 	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
-	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
 	"github.com/ava-labs/avalanchego/vms/platformvm/status"
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 
@@ -738,36 +737,7 @@ func (s *state) GetCurrentStakerIterator() (StakerIterator, error) {
 }
 
 func (s *state) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
-	stakerTx, _, err := s.GetTx(stakerID)
-	if err != nil {
-		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
-	}
-	switch uStakerTx := stakerTx.Unsigned.(type) {
-	case txs.ValidatorTx:
-		var pop *signer.ProofOfPossession
-		if staker, ok := uStakerTx.(*txs.AddPermissionlessValidatorTx); ok {
-			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
-				pop = s
-			}
-		}
-
-		return &StakerColdAttributes{
-			Stake:                  uStakerTx.Stake(),
-			Outputs:                uStakerTx.Outputs(),
-			Shares:                 uStakerTx.Shares(),
-			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
-			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
-			ProofOfPossession:      pop,
-		}, nil
-	case txs.DelegatorTx:
-		return &StakerColdAttributes{
-			Stake:        uStakerTx.Stake(),
-			Outputs:      uStakerTx.Outputs(),
-			RewardsOwner: uStakerTx.RewardsOwner(),
-		}, nil
-	default:
-		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
-	}
+	return getStakerColdAttributes(s, stakerID)
 }
 
 func (s *state) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
@@ -997,19 +967,7 @@ func (s *state) AddChain(createChainTxIntf *txs.Tx) {
 }
 
 func (s *state) GetChainSubnet(chainID ids.ID) (ids.ID, error) {
-	chainTx, _, err := s.GetTx(chainID)
-	if err != nil {
-		return ids.Empty, fmt.Errorf(
-			"problem retrieving blockchain %q: %w",
-			chainID,
-			err,
-		)
-	}
-	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
-	if !ok {
-		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
-	}
-	return chain.SubnetID, nil
+	return getChainSubnet(s, chainID)
 }
 
 func (s *state) getChainDB(subnetID ids.ID) linkeddb.LinkedDB {
diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
new file mode 100644
index 000000000000..ef7c108b28b5
--- /dev/null
+++ b/vms/platformvm/state/state_helpers.go
@@ -0,0 +1,61 @@
+// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package state
+
+import (
+	"fmt"
+
+	"github.com/ava-labs/avalanchego/ids"
+	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
+	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
+)
+
+func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttributes, error) {
+	stakerTx, _, err := chain.GetTx(stakerID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
+	}
+	switch uStakerTx := stakerTx.Unsigned.(type) {
+	case txs.ValidatorTx:
+		var pop *signer.ProofOfPossession
+		if staker, ok := uStakerTx.(*txs.AddPermissionlessValidatorTx); ok {
+			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
+				pop = s
+			}
+		}
+
+		return &StakerColdAttributes{
+			Stake:                  uStakerTx.Stake(),
+			Outputs:                uStakerTx.Outputs(),
+			Shares:                 uStakerTx.Shares(),
+			ValidationRewardsOwner: uStakerTx.ValidationRewardsOwner(),
+			DelegationRewardsOwner: uStakerTx.DelegationRewardsOwner(),
+			ProofOfPossession:      pop,
+		}, nil
+	case txs.DelegatorTx:
+		return &StakerColdAttributes{
+			Stake:        uStakerTx.Stake(),
+			Outputs:      uStakerTx.Outputs(),
+			RewardsOwner: uStakerTx.RewardsOwner(),
+		}, nil
+	default:
+		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
+	}
+}
+
+func getChainSubnet(chain Chain, chainID ids.ID) (ids.ID, error) {
+	chainTx, _, err := chain.GetTx(chainID)
+	if err != nil {
+		return ids.Empty, fmt.Errorf(
+			"problem retrieving blockchain %q: %w",
+			chainID,
+			err,
+		)
+	}
+	blockChain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
+	if !ok {
+		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
+	}
+	return blockChain.SubnetID, nil
+}

From 82759680de332a2ed3ffba2c52ce267e75402f02 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 13 Dec 2023 11:42:39 +0100
Subject: [PATCH 05/14] fix regression

---
 vms/platformvm/service.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index 2a9edb381c42..5133d87f6434 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -1943,8 +1943,14 @@ func (s *Service) chainExists(ctx context.Context, blockID ids.ID, chainID ids.I
 		}
 	}
 
-	_, err := state.GetChainSubnet(chainID)
-	return err == nil, err
+	switch _, err := state.GetChainSubnet(chainID); {
+	case err == nil:
+		return true, nil
+	case errors.Is(err, database.ErrNotFound):
+		return false, nil
+	default:
+		return false, err
+	}
 }
 
 // ValidatedByArgs is the arguments for calling ValidatedBy

From 54287cc1e13eedc5629a4a638d0e52cfb740fe7e Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 13:24:37 +0100
Subject: [PATCH 06/14] adding UTs for getStakerColdAttributes

---
 vms/platformvm/state/state_helpers.go      |   5 +-
 vms/platformvm/state/state_helpers_test.go | 231 +++++++++++++++++++++
 2 files changed, 235 insertions(+), 1 deletion(-)
 create mode 100644 vms/platformvm/state/state_helpers_test.go

diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index ef7c108b28b5..cca0a4f84d09 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -4,6 +4,7 @@
 package state
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/ava-labs/avalanchego/ids"
@@ -11,6 +12,8 @@ import (
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
 
+var errUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
+
 func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttributes, error) {
 	stakerTx, _, err := chain.GetTx(stakerID)
 	if err != nil {
@@ -40,7 +43,7 @@ func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttribute
 			RewardsOwner: uStakerTx.RewardsOwner(),
 		}, nil
 	default:
-		return nil, fmt.Errorf("unexpected stakerTx type %T", uStakerTx)
+		return nil, fmt.Errorf("%w, txType %T", errUnexpectedStakerTx, uStakerTx)
 	}
 }
 
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
new file mode 100644
index 000000000000..509cf407326b
--- /dev/null
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -0,0 +1,231 @@
+// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package state
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"go.uber.org/mock/gomock"
+
+	"github.com/ava-labs/avalanchego/database"
+	"github.com/ava-labs/avalanchego/ids"
+	"github.com/ava-labs/avalanchego/utils/crypto/bls"
+	"github.com/ava-labs/avalanchego/utils/units"
+	"github.com/ava-labs/avalanchego/vms/components/avax"
+	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
+	"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
+	"github.com/ava-labs/avalanchego/vms/platformvm/status"
+	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
+	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
+)
+
+func TestGetStakerColdAttributes(t *testing.T) {
+	type test struct {
+		name               string
+		chainF             func(*gomock.Controller) Chain
+		expectedAttributes *StakerColdAttributes
+		expectedErr        error
+	}
+
+	var (
+		stakerID    = ids.GenerateTestID()
+		shares      = uint32(2024)
+		avaxAssetID = ids.GenerateTestID()
+		addr        = ids.GenerateTestShortID()
+		outputs     = []*avax.TransferableOutput{
+			{
+				Asset: avax.Asset{
+					ID: avaxAssetID,
+				},
+				Out: &secp256k1fx.TransferOutput{
+					Amt: 1,
+					OutputOwners: secp256k1fx.OutputOwners{
+						Locktime:  0,
+						Threshold: 1,
+						Addrs: []ids.ShortID{
+							addr,
+						},
+					},
+				},
+			},
+			{
+				Asset: avax.Asset{
+					ID: avaxAssetID,
+				},
+				Out: &stakeable.LockOut{
+					Locktime: 87654321,
+					TransferableOut: &secp256k1fx.TransferOutput{
+						Amt: 1,
+						OutputOwners: secp256k1fx.OutputOwners{
+							Locktime:  12345678,
+							Threshold: 0,
+							Addrs:     []ids.ShortID{},
+						},
+					},
+				},
+			},
+		}
+		stakeOutputs = []*avax.TransferableOutput{
+			{
+				Asset: avax.Asset{
+					ID: avaxAssetID,
+				},
+				Out: &secp256k1fx.TransferOutput{
+					Amt: 2 * units.KiloAvax,
+					OutputOwners: secp256k1fx.OutputOwners{
+						Locktime:  0,
+						Threshold: 1,
+						Addrs: []ids.ShortID{
+							addr,
+						},
+					},
+				},
+			},
+		}
+		anOwner = &secp256k1fx.OutputOwners{
+			Locktime:  0,
+			Threshold: 1,
+			Addrs: []ids.ShortID{
+				addr,
+			},
+		}
+		anotherOwner = &secp256k1fx.OutputOwners{
+			Locktime:  0,
+			Threshold: 2,
+			Addrs: []ids.ShortID{
+				addr,
+			},
+		}
+	)
+	sk, err := bls.NewSecretKey()
+	require.NoError(t, err)
+	pop := signer.NewProofOfPossession(sk)
+
+	tests := []test{
+		{
+			name: "permissionless validator tx type",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				validatorTx := &txs.Tx{
+					Unsigned: &txs.AddPermissionlessValidatorTx{
+						BaseTx: txs.BaseTx{
+							BaseTx: avax.BaseTx{
+								Outs: outputs,
+							},
+						},
+						StakeOuts:             stakeOutputs,
+						ValidatorRewardsOwner: anOwner,
+						DelegatorRewardsOwner: anotherOwner,
+						DelegationShares:      shares,
+						Signer:                pop,
+					},
+				}
+				chain.EXPECT().GetTx(stakerID).Return(validatorTx, status.Committed, nil)
+				return chain
+			},
+			expectedAttributes: &StakerColdAttributes{
+				Stake:                  stakeOutputs,
+				Outputs:                outputs,
+				Shares:                 shares,
+				ValidationRewardsOwner: anOwner,
+				DelegationRewardsOwner: anotherOwner,
+				ProofOfPossession:      pop,
+			},
+			expectedErr: nil,
+		},
+		{
+			name: "non permissionless validator tx type",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				validatorTx := &txs.Tx{
+					Unsigned: &txs.AddValidatorTx{
+						BaseTx: txs.BaseTx{
+							BaseTx: avax.BaseTx{
+								Outs: outputs,
+							},
+						},
+						StakeOuts:        stakeOutputs,
+						RewardsOwner:     anOwner,
+						DelegationShares: shares,
+					},
+				}
+				chain.EXPECT().GetTx(stakerID).Return(validatorTx, status.Committed, nil)
+				return chain
+			},
+			expectedAttributes: &StakerColdAttributes{
+				Stake:                  stakeOutputs,
+				Outputs:                outputs,
+				Shares:                 shares,
+				ValidationRewardsOwner: anOwner,
+				DelegationRewardsOwner: anOwner,
+				ProofOfPossession:      nil,
+			},
+			expectedErr: nil,
+		},
+		{
+			name: "delegator tx type",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				delegatorTx := &txs.Tx{
+					Unsigned: &txs.AddPermissionlessDelegatorTx{
+						BaseTx: txs.BaseTx{
+							BaseTx: avax.BaseTx{
+								Outs: outputs,
+							},
+						},
+						StakeOuts:              stakeOutputs,
+						DelegationRewardsOwner: anOwner,
+					},
+				}
+				chain.EXPECT().GetTx(stakerID).Return(delegatorTx, status.Committed, nil)
+				return chain
+			},
+			expectedAttributes: &StakerColdAttributes{
+				Stake:        stakeOutputs,
+				Outputs:      outputs,
+				RewardsOwner: anOwner,
+			},
+			expectedErr: nil,
+		},
+		{
+			name: "missing tx",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				chain.EXPECT().GetTx(stakerID).Return(nil, status.Unknown, database.ErrNotFound)
+				return chain
+			},
+			expectedAttributes: nil,
+			expectedErr:        database.ErrNotFound,
+		},
+		{
+			name: "unexpected tx type",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				wrongTxType := &txs.Tx{
+					Unsigned: &txs.CreateChainTx{},
+				}
+				chain.EXPECT().GetTx(stakerID).Return(wrongTxType, status.Committed, nil)
+				return chain
+			},
+			expectedAttributes: nil,
+			expectedErr:        errUnexpectedStakerTx,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			require := require.New(t)
+			ctrl := gomock.NewController(t)
+
+			chain := tt.chainF(ctrl)
+			attributes, err := getStakerColdAttributes(chain, stakerID)
+			require.ErrorIs(err, tt.expectedErr)
+			if tt.expectedErr != nil {
+				return
+			}
+			require.Equal(tt.expectedAttributes, attributes)
+		})
+	}
+}

From e8305e27852bf257f6285ec9285b9717a36e6383 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 14:23:08 +0100
Subject: [PATCH 07/14] some more UTs

---
 vms/platformvm/state/state_helpers.go      |  7 ++-
 vms/platformvm/state/state_helpers_test.go | 71 ++++++++++++++++++++++
 2 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index cca0a4f84d09..839ebfe4b3ba 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -12,7 +12,10 @@ import (
 	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
 )
 
-var errUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
+var (
+	errUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
+	errNotABlockchain     = errors.New("tx does not created a blockchain")
+)
 
 func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttributes, error) {
 	stakerTx, _, err := chain.GetTx(stakerID)
@@ -58,7 +61,7 @@ func getChainSubnet(chain Chain, chainID ids.ID) (ids.ID, error) {
 	}
 	blockChain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
 	if !ok {
-		return ids.Empty, fmt.Errorf("%q is not a blockchain", chainID)
+		return ids.Empty, fmt.Errorf("%w, txID %q", errNotABlockchain, chainID)
 	}
 	return blockChain.SubnetID, nil
 }
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index 509cf407326b..d025f7ca58eb 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -229,3 +229,74 @@ func TestGetStakerColdAttributes(t *testing.T) {
 		})
 	}
 }
+
+func TestGetChainSubnet(t *testing.T) {
+	type test struct {
+		name             string
+		chainF           func(*gomock.Controller) Chain
+		expectedSubnetID ids.ID
+		expectedErr      error
+	}
+
+	var (
+		chainID  = ids.GenerateTestID()
+		subnetID = ids.GenerateTestID()
+	)
+
+	tests := []test{
+		{
+			name: "subnet from existing chain",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				createChainTx := &txs.Tx{
+					Unsigned: &txs.CreateChainTx{
+						SubnetID: subnetID,
+					},
+					TxID: chainID,
+				}
+				chain.EXPECT().GetTx(chainID).Return(createChainTx, status.Committed, nil)
+				return chain
+			},
+			expectedSubnetID: subnetID,
+			expectedErr:      nil,
+		},
+		{
+			name: "missing tx",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				chain.EXPECT().GetTx(chainID).Return(nil, status.Unknown, database.ErrNotFound)
+				return chain
+			},
+			expectedSubnetID: ids.Empty,
+			expectedErr:      database.ErrNotFound,
+		},
+		{
+			name: "unexpected tx type",
+			chainF: func(c *gomock.Controller) Chain {
+				chain := NewMockChain(c)
+				wrongTxType := &txs.Tx{
+					Unsigned: &txs.CreateSubnetTx{},
+				}
+				chain.EXPECT().GetTx(chainID).Return(wrongTxType, status.Committed, nil)
+				return chain
+			},
+			expectedSubnetID: ids.Empty,
+			expectedErr:      errNotABlockchain,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			require := require.New(t)
+			ctrl := gomock.NewController(t)
+
+			chain := tt.chainF(ctrl)
+			subnetID, err := getChainSubnet(chain, chainID)
+			require.ErrorIs(err, tt.expectedErr)
+			if tt.expectedErr != nil {
+				return
+			}
+			require.Equal(tt.expectedSubnetID, subnetID)
+		})
+	}
+}

From c311437fc601851441c1420bd8da0f570b5f63fb Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 14:29:49 +0100
Subject: [PATCH 08/14] renamed cold attributes to reward attributes

---
 vms/platformvm/service.go                     |  6 +--
 vms/platformvm/service_test.go                |  2 +-
 vms/platformvm/state/diff.go                  |  4 +-
 vms/platformvm/state/mock_state.go            | 42 +++++++++----------
 vms/platformvm/state/staker.go                |  5 +--
 vms/platformvm/state/state.go                 |  6 +--
 vms/platformvm/state/state_helpers.go         |  6 +--
 vms/platformvm/state/state_helpers_test.go    | 12 +++---
 .../txs/executor/proposal_tx_executor.go      |  8 ++--
 vms/platformvm/vm.go                          |  2 +-
 10 files changed, 46 insertions(+), 47 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index 5133d87f6434..7a7b1d893aa8 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -84,7 +84,7 @@ var (
 type Service struct {
 	vm                    *VM
 	addrManager           avax.AddressManager
-	stakerAttributesCache *cache.LRU[ids.ID, *state.StakerColdAttributes]
+	stakerAttributesCache *cache.LRU[ids.ID, *state.StakerRewardAttributes]
 }
 
 // GetHeight returns the height of the last accepted block
@@ -715,7 +715,7 @@ type GetCurrentValidatorsReply struct {
 	Validators []interface{} `json:"validators"`
 }
 
-func (s *Service) loadStakerTxAttributes(txID ids.ID) (*state.StakerColdAttributes, error) {
+func (s *Service) loadStakerTxAttributes(txID ids.ID) (*state.StakerRewardAttributes, error) {
 	// Lookup tx from the cache first.
 	attr, found := s.stakerAttributesCache.Get(txID)
 	if found {
@@ -723,7 +723,7 @@ func (s *Service) loadStakerTxAttributes(txID ids.ID) (*state.StakerColdAttribut
 	}
 
 	// Tx not available in cache; pull it from disk and populate the cache.
-	attr, err := s.vm.state.GetStakerColdAttributes(txID)
+	attr, err := s.vm.state.GetStakerRewardAttributes(txID)
 	if err != nil {
 		return nil, err
 	}
diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go
index 90c2b4a32a0f..dc0820c73da1 100644
--- a/vms/platformvm/service_test.go
+++ b/vms/platformvm/service_test.go
@@ -83,7 +83,7 @@ func defaultService(t *testing.T) (*Service, *mutableSharedMemory) {
 	return &Service{
 		vm:          vm,
 		addrManager: avax.NewAddressManager(vm.ctx),
-		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerColdAttributes]{
+		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerRewardAttributes]{
 			Size: stakerAttributesCacheSize,
 		},
 	}, mutableSharedMemory
diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go
index 3e395cdf4ed7..d429fc74317d 100644
--- a/vms/platformvm/state/diff.go
+++ b/vms/platformvm/state/diff.go
@@ -209,8 +209,8 @@ func (d *diff) GetCurrentStakerIterator() (StakerIterator, error) {
 	return d.currentStakerDiffs.GetStakerIterator(parentIterator), nil
 }
 
-func (d *diff) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
-	return getStakerColdAttributes(d, stakerID)
+func (d *diff) GetStakerRewardAttributes(stakerID ids.ID) (*StakerRewardAttributes, error) {
+	return getStakerRewardAttributes(d, stakerID)
 }
 
 func (d *diff) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go
index dceff53e3f5d..d1c312a6106d 100644
--- a/vms/platformvm/state/mock_state.go
+++ b/vms/platformvm/state/mock_state.go
@@ -315,19 +315,19 @@ func (mr *MockChainMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), arg0, arg1)
 }
 
-// GetStakerColdAttributes mocks base method.
-func (m *MockChain) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+// GetStakerRewardAttributes mocks base method.
+func (m *MockChain) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttributes, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
-	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret := m.ctrl.Call(m, "GetStakerRewardAttributes", arg0)
+	ret0, _ := ret[0].(*StakerRewardAttributes)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
-func (mr *MockChainMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+// GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
+func (mr *MockChainMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockChain)(nil).GetStakerColdAttributes), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockChain)(nil).GetStakerRewardAttributes), arg0)
 }
 
 // GetSubnetOwner mocks base method.
@@ -807,19 +807,19 @@ func (mr *MockDiffMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *gom
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), arg0, arg1)
 }
 
-// GetStakerColdAttributes mocks base method.
-func (m *MockDiff) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+// GetStakerRewardAttributes mocks base method.
+func (m *MockDiff) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttributes, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
-	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret := m.ctrl.Call(m, "GetStakerRewardAttributes", arg0)
+	ret0, _ := ret[0].(*StakerRewardAttributes)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
-func (mr *MockDiffMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+// GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
+func (mr *MockDiffMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockDiff)(nil).GetStakerColdAttributes), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockDiff)(nil).GetStakerRewardAttributes), arg0)
 }
 
 // GetSubnetOwner mocks base method.
@@ -1453,19 +1453,19 @@ func (mr *MockStateMockRecorder) GetRewardUTXOs(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), arg0)
 }
 
-// GetStakerColdAttributes mocks base method.
-func (m *MockState) GetStakerColdAttributes(arg0 ids.ID) (*StakerColdAttributes, error) {
+// GetStakerRewardAttributes mocks base method.
+func (m *MockState) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttributes, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetStakerColdAttributes", arg0)
-	ret0, _ := ret[0].(*StakerColdAttributes)
+	ret := m.ctrl.Call(m, "GetStakerRewardAttributes", arg0)
+	ret0, _ := ret[0].(*StakerRewardAttributes)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// GetStakerColdAttributes indicates an expected call of GetStakerColdAttributes.
-func (mr *MockStateMockRecorder) GetStakerColdAttributes(arg0 interface{}) *gomock.Call {
+// GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
+func (mr *MockStateMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerColdAttributes", reflect.TypeOf((*MockState)(nil).GetStakerColdAttributes), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockState)(nil).GetStakerRewardAttributes), arg0)
 }
 
 // GetStartTime mocks base method.
diff --git a/vms/platformvm/state/staker.go b/vms/platformvm/state/staker.go
index df8cb69f22c4..ef3848c3513f 100644
--- a/vms/platformvm/state/staker.go
+++ b/vms/platformvm/state/staker.go
@@ -131,12 +131,11 @@ func NewPendingStaker(txID ids.ID, staker txs.ScheduledStaker) (*Staker, error)
 }
 
 // While Staker object contains a staker's hot attributes, likely to be used pretty often
-// StakerColdAttributes contains a staker's cold attributes, rarely used, mostly when rewarding it.
+// StakerRewardAttributes contains a staker's cold attributes, rarely used, mostly when rewarding it.
 // Note that both Staker and StakerAttribute content comes from the stakerTx creating the staker.
 // In state.State we do have StakerMetadata information as well, which contains data about the stakers
 // that are generated during staker's activity (mostly uptimes)
-// TODO: consider moving delegatees reward here, out of StakersMetadata.
-type StakerColdAttributes struct {
+type StakerRewardAttributes struct {
 	// common attributes
 	Stake   []*avax.TransferableOutput
 	Outputs []*avax.TransferableOutput
diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go
index 03ed8eb561b1..42545f0ddcd5 100644
--- a/vms/platformvm/state/state.go
+++ b/vms/platformvm/state/state.go
@@ -119,7 +119,7 @@ type Chain interface {
 	AddChain(createChainTx *txs.Tx)
 	GetChainSubnet(chainID ids.ID) (ids.ID, error)
 
-	GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error)
+	GetStakerRewardAttributes(stakerID ids.ID) (*StakerRewardAttributes, error)
 
 	GetTx(txID ids.ID) (*txs.Tx, status.Status, error)
 	AddTx(tx *txs.Tx, status status.Status)
@@ -738,8 +738,8 @@ func (s *state) GetCurrentStakerIterator() (StakerIterator, error) {
 	return s.currentStakers.GetStakerIterator(), nil
 }
 
-func (s *state) GetStakerColdAttributes(stakerID ids.ID) (*StakerColdAttributes, error) {
-	return getStakerColdAttributes(s, stakerID)
+func (s *state) GetStakerRewardAttributes(stakerID ids.ID) (*StakerRewardAttributes, error) {
+	return getStakerRewardAttributes(s, stakerID)
 }
 
 func (s *state) GetPendingValidator(subnetID ids.ID, nodeID ids.NodeID) (*Staker, error) {
diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index 839ebfe4b3ba..6ba86e56a13b 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -17,7 +17,7 @@ var (
 	errNotABlockchain     = errors.New("tx does not created a blockchain")
 )
 
-func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttributes, error) {
+func getStakerRewardAttributes(chain Chain, stakerID ids.ID) (*StakerRewardAttributes, error) {
 	stakerTx, _, err := chain.GetTx(stakerID)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get next staker %s: %w", stakerID, err)
@@ -31,7 +31,7 @@ func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttribute
 			}
 		}
 
-		return &StakerColdAttributes{
+		return &StakerRewardAttributes{
 			Stake:                  uStakerTx.Stake(),
 			Outputs:                uStakerTx.Outputs(),
 			Shares:                 uStakerTx.Shares(),
@@ -40,7 +40,7 @@ func getStakerColdAttributes(chain Chain, stakerID ids.ID) (*StakerColdAttribute
 			ProofOfPossession:      pop,
 		}, nil
 	case txs.DelegatorTx:
-		return &StakerColdAttributes{
+		return &StakerRewardAttributes{
 			Stake:        uStakerTx.Stake(),
 			Outputs:      uStakerTx.Outputs(),
 			RewardsOwner: uStakerTx.RewardsOwner(),
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index d025f7ca58eb..f90d6b7b2031 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -21,11 +21,11 @@ import (
 	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
 )
 
-func TestGetStakerColdAttributes(t *testing.T) {
+func TestGetStakerRewardAttributes(t *testing.T) {
 	type test struct {
 		name               string
 		chainF             func(*gomock.Controller) Chain
-		expectedAttributes *StakerColdAttributes
+		expectedAttributes *StakerRewardAttributes
 		expectedErr        error
 	}
 
@@ -125,7 +125,7 @@ func TestGetStakerColdAttributes(t *testing.T) {
 				chain.EXPECT().GetTx(stakerID).Return(validatorTx, status.Committed, nil)
 				return chain
 			},
-			expectedAttributes: &StakerColdAttributes{
+			expectedAttributes: &StakerRewardAttributes{
 				Stake:                  stakeOutputs,
 				Outputs:                outputs,
 				Shares:                 shares,
@@ -154,7 +154,7 @@ func TestGetStakerColdAttributes(t *testing.T) {
 				chain.EXPECT().GetTx(stakerID).Return(validatorTx, status.Committed, nil)
 				return chain
 			},
-			expectedAttributes: &StakerColdAttributes{
+			expectedAttributes: &StakerRewardAttributes{
 				Stake:                  stakeOutputs,
 				Outputs:                outputs,
 				Shares:                 shares,
@@ -182,7 +182,7 @@ func TestGetStakerColdAttributes(t *testing.T) {
 				chain.EXPECT().GetTx(stakerID).Return(delegatorTx, status.Committed, nil)
 				return chain
 			},
-			expectedAttributes: &StakerColdAttributes{
+			expectedAttributes: &StakerRewardAttributes{
 				Stake:        stakeOutputs,
 				Outputs:      outputs,
 				RewardsOwner: anOwner,
@@ -220,7 +220,7 @@ func TestGetStakerColdAttributes(t *testing.T) {
 			ctrl := gomock.NewController(t)
 
 			chain := tt.chainF(ctrl)
-			attributes, err := getStakerColdAttributes(chain, stakerID)
+			attributes, err := getStakerRewardAttributes(chain, stakerID)
 			require.ErrorIs(err, tt.expectedErr)
 			if tt.expectedErr != nil {
 				return
diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go
index 8c96abbf4661..31834c2c9e71 100644
--- a/vms/platformvm/txs/executor/proposal_tx_executor.go
+++ b/vms/platformvm/txs/executor/proposal_tx_executor.go
@@ -363,7 +363,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 		return err
 	}
 
-	stakerAttributes, err := e.OnCommitState.GetStakerColdAttributes(stakerToReward.TxID)
+	stakerAttributes, err := e.OnCommitState.GetStakerRewardAttributes(stakerToReward.TxID)
 	if err != nil {
 		return fmt.Errorf("failed to get attributes for staker %d: %w", stakerToReward.TxID, err)
 	}
@@ -411,7 +411,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 	return err
 }
 
-func (e *ProposalTxExecutor) rewardValidatorTx(validator *state.Staker, valAttributes *state.StakerColdAttributes) error {
+func (e *ProposalTxExecutor) rewardValidatorTx(validator *state.Staker, valAttributes *state.StakerRewardAttributes) error {
 	var (
 		txID    = validator.TxID
 		stake   = valAttributes.Stake
@@ -513,7 +513,7 @@ func (e *ProposalTxExecutor) rewardValidatorTx(validator *state.Staker, valAttri
 	return nil
 }
 
-func (e *ProposalTxExecutor) rewardDelegatorTx(delegator *state.Staker, delAttributes *state.StakerColdAttributes) error {
+func (e *ProposalTxExecutor) rewardDelegatorTx(delegator *state.Staker, delAttributes *state.StakerRewardAttributes) error {
 	var (
 		txID    = delegator.TxID
 		stake   = delAttributes.Stake
@@ -544,7 +544,7 @@ func (e *ProposalTxExecutor) rewardDelegatorTx(delegator *state.Staker, delAttri
 		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
 	}
 
-	valAttributes, err := e.OnCommitState.GetStakerColdAttributes(validator.TxID)
+	valAttributes, err := e.OnCommitState.GetStakerRewardAttributes(validator.TxID)
 	if err != nil {
 		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
 	}
diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go
index 449ba86404b1..19c19437602d 100644
--- a/vms/platformvm/vm.go
+++ b/vms/platformvm/vm.go
@@ -420,7 +420,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {
 	service := &Service{
 		vm:          vm,
 		addrManager: avax.NewAddressManager(vm.ctx),
-		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerColdAttributes]{
+		stakerAttributesCache: &cache.LRU[ids.ID, *state.StakerRewardAttributes]{
 			Size: stakerAttributesCacheSize,
 		},
 	}

From a6d02faa563ac41b3d633df289bd8debc843d47e Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 14:44:08 +0100
Subject: [PATCH 09/14] some more use of staker reward attributes

---
 vms/platformvm/service.go | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index 7a7b1d893aa8..48c2ff34851a 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -2298,12 +2298,12 @@ func (s *Service) GetStake(_ *http.Request, args *GetStakeArgs, response *GetSta
 			continue
 		}
 
-		tx, _, err := s.vm.state.GetTx(staker.TxID)
+		stakerRewardAttributes, err := s.vm.state.GetStakerRewardAttributes(staker.TxID)
 		if err != nil {
 			return err
 		}
 
-		stakedOuts = append(stakedOuts, getStakeHelper(tx, addrs, totalAmountStaked)...)
+		stakedOuts = append(stakedOuts, getStakeHelper(stakerRewardAttributes.Stake, addrs, totalAmountStaked)...)
 	}
 
 	pendingStakerIterator, err := s.vm.state.GetPendingStakerIterator()
@@ -2319,12 +2319,12 @@ func (s *Service) GetStake(_ *http.Request, args *GetStakeArgs, response *GetSta
 			continue
 		}
 
-		tx, _, err := s.vm.state.GetTx(staker.TxID)
+		stakerRewardAttributes, err := s.vm.state.GetStakerRewardAttributes(staker.TxID)
 		if err != nil {
 			return err
 		}
 
-		stakedOuts = append(stakedOuts, getStakeHelper(tx, addrs, totalAmountStaked)...)
+		stakedOuts = append(stakedOuts, getStakeHelper(stakerRewardAttributes.Stake, addrs, totalAmountStaked)...)
 	}
 
 	response.Stakeds = newJSONBalanceMap(totalAmountStaked)
@@ -2751,13 +2751,7 @@ func (s *Service) getAPIOwner(owner *secp256k1fx.OutputOwners) (*platformapi.Own
 // Returns:
 // 1) The total amount staked by addresses in [addrs]
 // 2) The staked outputs
-func getStakeHelper(tx *txs.Tx, addrs set.Set[ids.ShortID], totalAmountStaked map[ids.ID]uint64) []avax.TransferableOutput {
-	staker, ok := tx.Unsigned.(txs.PermissionlessStaker)
-	if !ok {
-		return nil
-	}
-
-	stake := staker.Stake()
+func getStakeHelper(stake []*avax.TransferableOutput, addrs set.Set[ids.ShortID], totalAmountStaked map[ids.ID]uint64) []avax.TransferableOutput {
 	stakedOuts := make([]avax.TransferableOutput, 0, len(stake))
 	// Go through all of the staked outputs
 	for _, output := range stake {

From 1255c4f2257aafb2fcf3717de23de02d4652fc79 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 22:08:54 +0100
Subject: [PATCH 10/14] nits

---
 vms/platformvm/service.go                           | 6 +++---
 vms/platformvm/state/staker.go                      | 8 ++++----
 vms/platformvm/state/state_helpers.go               | 4 ++--
 vms/platformvm/state/state_helpers_test.go          | 2 +-
 vms/platformvm/txs/executor/proposal_tx_executor.go | 4 ++--
 5 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go
index 48c2ff34851a..3f70fb393d03 100644
--- a/vms/platformvm/service.go
+++ b/vms/platformvm/service.go
@@ -716,13 +716,13 @@ type GetCurrentValidatorsReply struct {
 }
 
 func (s *Service) loadStakerTxAttributes(txID ids.ID) (*state.StakerRewardAttributes, error) {
-	// Lookup tx from the cache first.
+	// Lookup attributes from the cache first.
 	attr, found := s.stakerAttributesCache.Get(txID)
 	if found {
 		return attr, nil
 	}
 
-	// Tx not available in cache; pull it from disk and populate the cache.
+	// attributes not available in cache; pull them from disk and populate the cache.
 	attr, err := s.vm.state.GetStakerRewardAttributes(txID)
 	if err != nil {
 		return nil, err
@@ -2747,7 +2747,7 @@ func (s *Service) getAPIOwner(owner *secp256k1fx.OutputOwners) (*platformapi.Own
 	return apiOwner, nil
 }
 
-// Takes in a staker and a set of addresses
+// Takes in a slice of reward attributes and a set of addresses
 // Returns:
 // 1) The total amount staked by addresses in [addrs]
 // 2) The staked outputs
diff --git a/vms/platformvm/state/staker.go b/vms/platformvm/state/staker.go
index ef3848c3513f..2cd764e22f3b 100644
--- a/vms/platformvm/state/staker.go
+++ b/vms/platformvm/state/staker.go
@@ -130,11 +130,11 @@ func NewPendingStaker(txID ids.ID, staker txs.ScheduledStaker) (*Staker, error)
 	}, nil
 }
 
-// While Staker object contains a staker's hot attributes, likely to be used pretty often
-// StakerRewardAttributes contains a staker's cold attributes, rarely used, mostly when rewarding it.
+// Staker object contains a staker's hot attributes, likely to be used often.
+// StakerRewardAttributes contains a staker's cold attributes which are used less often.
 // Note that both Staker and StakerAttribute content comes from the stakerTx creating the staker.
-// In state.State we do have StakerMetadata information as well, which contains data about the stakers
-// that are generated during staker's activity (mostly uptimes)
+// In state.State we also have StakerMetadata, which contains data about the stakers
+// generated during staker's activity (mostly uptimes).
 type StakerRewardAttributes struct {
 	// common attributes
 	Stake   []*avax.TransferableOutput
diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index 6ba86e56a13b..e286c7cde56d 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -13,7 +13,7 @@ import (
 )
 
 var (
-	errUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
+	ErrUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
 	errNotABlockchain     = errors.New("tx does not created a blockchain")
 )
 
@@ -46,7 +46,7 @@ func getStakerRewardAttributes(chain Chain, stakerID ids.ID) (*StakerRewardAttri
 			RewardsOwner: uStakerTx.RewardsOwner(),
 		}, nil
 	default:
-		return nil, fmt.Errorf("%w, txType %T", errUnexpectedStakerTx, uStakerTx)
+		return nil, fmt.Errorf("%w, txType %T", ErrUnexpectedStakerTx, uStakerTx)
 	}
 }
 
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index f90d6b7b2031..0dc2637661bd 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -210,7 +210,7 @@ func TestGetStakerRewardAttributes(t *testing.T) {
 				return chain
 			},
 			expectedAttributes: nil,
-			expectedErr:        errUnexpectedStakerTx,
+			expectedErr:        ErrUnexpectedStakerTx,
 		},
 	}
 
diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go
index 31834c2c9e71..8e3df3ee6eeb 100644
--- a/vms/platformvm/txs/executor/proposal_tx_executor.go
+++ b/vms/platformvm/txs/executor/proposal_tx_executor.go
@@ -365,7 +365,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 
 	stakerAttributes, err := e.OnCommitState.GetStakerRewardAttributes(stakerToReward.TxID)
 	if err != nil {
-		return fmt.Errorf("failed to get attributes for staker %d: %w", stakerToReward.TxID, err)
+		return fmt.Errorf("failed to get attributes for staker %s: %w", stakerToReward.TxID, err)
 	}
 
 	switch {
@@ -392,7 +392,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 		e.OnCommitState.DeleteCurrentDelegator(stakerToReward)
 		e.OnAbortState.DeleteCurrentDelegator(stakerToReward)
 	default:
-		return errors.New("unexpected staker type")
+		return state.ErrUnexpectedStakerTx
 	}
 
 	// If the reward is aborted, then the current supply should be decreased.

From de5c35c3d797e8a47bee6ba956556a36560b130c Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Wed, 20 Dec 2023 22:23:48 +0100
Subject: [PATCH 11/14] nit

---
 vms/platformvm/state/state_helpers.go               | 6 +++---
 vms/platformvm/state/state_helpers_test.go          | 2 +-
 vms/platformvm/txs/executor/proposal_tx_executor.go | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index e286c7cde56d..f48d25209400 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -13,8 +13,8 @@ import (
 )
 
 var (
-	ErrUnexpectedStakerTx = errors.New("unexpected stakerTx type ")
-	errNotABlockchain     = errors.New("tx does not created a blockchain")
+	ErrUnexpectedStakerType = errors.New("unexpected staker type ")
+	errNotABlockchain       = errors.New("tx does not created a blockchain")
 )
 
 func getStakerRewardAttributes(chain Chain, stakerID ids.ID) (*StakerRewardAttributes, error) {
@@ -46,7 +46,7 @@ func getStakerRewardAttributes(chain Chain, stakerID ids.ID) (*StakerRewardAttri
 			RewardsOwner: uStakerTx.RewardsOwner(),
 		}, nil
 	default:
-		return nil, fmt.Errorf("%w, txType %T", ErrUnexpectedStakerTx, uStakerTx)
+		return nil, fmt.Errorf("%w, txType %T", ErrUnexpectedStakerType, uStakerTx)
 	}
 }
 
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index 0dc2637661bd..fe2dcc9371cb 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -210,7 +210,7 @@ func TestGetStakerRewardAttributes(t *testing.T) {
 				return chain
 			},
 			expectedAttributes: nil,
-			expectedErr:        ErrUnexpectedStakerTx,
+			expectedErr:        ErrUnexpectedStakerType,
 		},
 	}
 
diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go
index 8e3df3ee6eeb..d012ecea7d3b 100644
--- a/vms/platformvm/txs/executor/proposal_tx_executor.go
+++ b/vms/platformvm/txs/executor/proposal_tx_executor.go
@@ -392,7 +392,7 @@ func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error
 		e.OnCommitState.DeleteCurrentDelegator(stakerToReward)
 		e.OnAbortState.DeleteCurrentDelegator(stakerToReward)
 	default:
-		return state.ErrUnexpectedStakerTx
+		return state.ErrUnexpectedStakerType
 	}
 
 	// If the reward is aborted, then the current supply should be decreased.

From b0f525bcd79d2d6a8729eb9ef546d5751f2f6e23 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Thu, 11 Jan 2024 10:03:53 +0100
Subject: [PATCH 12/14] added UTs

---
 vms/platformvm/state/state_helpers_test.go | 68 ++++++++++++++++++++++
 1 file changed, 68 insertions(+)

diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index fe2dcc9371cb..9925109d9091 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -5,6 +5,7 @@ package state
 
 import (
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 	"go.uber.org/mock/gomock"
@@ -212,6 +213,47 @@ func TestGetStakerRewardAttributes(t *testing.T) {
 			expectedAttributes: nil,
 			expectedErr:        ErrUnexpectedStakerType,
 		},
+		{
+			name: "getStakerRewardAttributes works across layers",
+			chainF: func(c *gomock.Controller) Chain {
+				// pile a diff on top of base state and let the target tx
+				// be included in base state.
+				state := NewMockState(c)
+				validatorTx := &txs.Tx{
+					Unsigned: &txs.AddPermissionlessValidatorTx{
+						BaseTx: txs.BaseTx{
+							BaseTx: avax.BaseTx{
+								Outs: outputs,
+							},
+						},
+						StakeOuts:             stakeOutputs,
+						ValidatorRewardsOwner: anOwner,
+						DelegatorRewardsOwner: anotherOwner,
+						DelegationShares:      shares,
+						Signer:                pop,
+					},
+				}
+				state.EXPECT().GetTx(stakerID).Return(validatorTx, status.Committed, nil)
+				state.EXPECT().GetTimestamp().Return(time.Now()) // needed to build diff
+				stateID := ids.GenerateTestID()
+
+				versions := NewMockVersions(c)
+				versions.EXPECT().GetState(stateID).Return(state, true).Times(2)
+
+				diff, err := NewDiff(stateID, versions)
+				require.NoError(t, err)
+				return diff
+			},
+			expectedAttributes: &StakerRewardAttributes{
+				Stake:                  stakeOutputs,
+				Outputs:                outputs,
+				Shares:                 shares,
+				ValidationRewardsOwner: anOwner,
+				DelegationRewardsOwner: anotherOwner,
+				ProofOfPossession:      pop,
+			},
+			expectedErr: nil,
+		},
 	}
 
 	for _, tt := range tests {
@@ -283,6 +325,32 @@ func TestGetChainSubnet(t *testing.T) {
 			expectedSubnetID: ids.Empty,
 			expectedErr:      errNotABlockchain,
 		},
+		{
+			name: "getChainSubnet works across layers",
+			chainF: func(c *gomock.Controller) Chain {
+				// pile a diff on top of base state and let the target tx
+				// be included in base state.
+				state := NewMockState(c)
+				createChainTx := &txs.Tx{
+					Unsigned: &txs.CreateChainTx{
+						SubnetID: subnetID,
+					},
+					TxID: chainID,
+				}
+				state.EXPECT().GetTx(chainID).Return(createChainTx, status.Committed, nil)
+				state.EXPECT().GetTimestamp().Return(time.Now()) // needed to build diff
+				stateID := ids.GenerateTestID()
+
+				versions := NewMockVersions(c)
+				versions.EXPECT().GetState(stateID).Return(state, true).Times(2)
+
+				diff, err := NewDiff(stateID, versions)
+				require.NoError(t, err)
+				return diff
+			},
+			expectedSubnetID: subnetID,
+			expectedErr:      nil,
+		},
 	}
 
 	for _, tt := range tests {

From 297a490ce2c8a2d40fa69c453c1fb14b1b2366d1 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Thu, 18 Jan 2024 09:26:27 +0100
Subject: [PATCH 13/14] appease linter

---
 vms/platformvm/state/state_helpers.go      | 2 +-
 vms/platformvm/state/state_helpers_test.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/vms/platformvm/state/state_helpers.go b/vms/platformvm/state/state_helpers.go
index f48d25209400..6726c0162966 100644
--- a/vms/platformvm/state/state_helpers.go
+++ b/vms/platformvm/state/state_helpers.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
+// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
 // See the file LICENSE for licensing terms.
 
 package state
diff --git a/vms/platformvm/state/state_helpers_test.go b/vms/platformvm/state/state_helpers_test.go
index 9925109d9091..6ad5eaff903e 100644
--- a/vms/platformvm/state/state_helpers_test.go
+++ b/vms/platformvm/state/state_helpers_test.go
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
+// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
 // See the file LICENSE for licensing terms.
 
 package state

From 61cfc547027fbe51fcffc958e4c47c38aff58893 Mon Sep 17 00:00:00 2001
From: Alberto Benegiamo <alberto.benegiamo@gmail.com>
Date: Fri, 19 Jan 2024 15:02:51 +0100
Subject: [PATCH 14/14] fixed mock regeneration

---
 vms/platformvm/state/mock_state.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go
index 07c99ccc0e46..ca405cb421a8 100644
--- a/vms/platformvm/state/mock_state.go
+++ b/vms/platformvm/state/mock_state.go
@@ -192,7 +192,7 @@ func (m *MockChain) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
 }
 
 // GetChainSubnet indicates an expected call of GetChainSubnet.
-func (mr *MockChainMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+func (mr *MockChainMockRecorder) GetChainSubnet(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockChain)(nil).GetChainSubnet), arg0)
 }
@@ -327,7 +327,7 @@ func (m *MockChain) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttribu
 }
 
 // GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
-func (mr *MockChainMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
+func (mr *MockChainMockRecorder) GetStakerRewardAttributes(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockChain)(nil).GetStakerRewardAttributes), arg0)
 }
@@ -684,7 +684,7 @@ func (m *MockDiff) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
 }
 
 // GetChainSubnet indicates an expected call of GetChainSubnet.
-func (mr *MockDiffMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+func (mr *MockDiffMockRecorder) GetChainSubnet(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockDiff)(nil).GetChainSubnet), arg0)
 }
@@ -819,7 +819,7 @@ func (m *MockDiff) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttribut
 }
 
 // GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
-func (mr *MockDiffMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
+func (mr *MockDiffMockRecorder) GetStakerRewardAttributes(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockDiff)(nil).GetStakerRewardAttributes), arg0)
 }
@@ -1286,7 +1286,7 @@ func (m *MockState) GetChainSubnet(arg0 ids.ID) (ids.ID, error) {
 }
 
 // GetChainSubnet indicates an expected call of GetChainSubnet.
-func (mr *MockStateMockRecorder) GetChainSubnet(arg0 interface{}) *gomock.Call {
+func (mr *MockStateMockRecorder) GetChainSubnet(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainSubnet", reflect.TypeOf((*MockState)(nil).GetChainSubnet), arg0)
 }
@@ -1465,7 +1465,7 @@ func (m *MockState) GetStakerRewardAttributes(arg0 ids.ID) (*StakerRewardAttribu
 }
 
 // GetStakerRewardAttributes indicates an expected call of GetStakerRewardAttributes.
-func (mr *MockStateMockRecorder) GetStakerRewardAttributes(arg0 interface{}) *gomock.Call {
+func (mr *MockStateMockRecorder) GetStakerRewardAttributes(arg0 any) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStakerRewardAttributes", reflect.TypeOf((*MockState)(nil).GetStakerRewardAttributes), arg0)
 }