Skip to content

Commit

Permalink
Added check for unstaking operators
Browse files Browse the repository at this point in the history
  • Loading branch information
tomaszslabon committed May 15, 2024
1 parent 01d69fc commit e5d61e7
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 25 deletions.
6 changes: 0 additions & 6 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2363,9 +2363,3 @@ func (tc *TbtcChain) GetRedemptionDelay(
func (tc *TbtcChain) GetDepositMinAge() (uint32, error) {
return tc.walletProposalValidator.DEPOSITMINAGE()
}

func (tc *TbtcChain) IsOperatorUnstaking() (bool, error) {
// TODO: Implement by checking if the operator has deauthorized their entire
// stake.
return false, nil
}
9 changes: 0 additions & 9 deletions pkg/tbtc/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ const (
Challenge
)

// StakingChain defines the subset of the TBTC chain interface that pertains to
// the staking activities.
type StakingChain interface {
// IsOperatorUnstaking checks if the operator is unstaking. It returns true
// if the operator has deauthorized their entire stake, false otherwise.
IsOperatorUnstaking() (bool, error)
}

// GroupSelectionChain defines the subset of the TBTC chain interface that
// pertains to the group selection activities.
type GroupSelectionChain interface {
Expand Down Expand Up @@ -539,7 +531,6 @@ type Chain interface {
GetBlockHashByNumber(blockNumber uint64) ([32]byte, error)

sortition.Chain
StakingChain
GroupSelectionChain
DistributedKeyGenerationChain
InactivityClaimChain
Expand Down
33 changes: 25 additions & 8 deletions pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import (
"github.com/keep-network/keep-core/pkg/tecdsa/dkg"
)

const localChainOperatorID = chain.OperatorID(1)
const (
localChainOperatorID = chain.OperatorID(1)
stakingProvider = chain.Address("0x1111111111111111111111111111111111111111")
)

type movingFundsParameters = struct {
txMaxTotalFee uint64
Expand Down Expand Up @@ -108,6 +111,9 @@ type localChain struct {
movingFundsParametersMutex sync.Mutex
movingFundsParameters movingFundsParameters

eligibleStakesMutex sync.Mutex
eligibleStakes map[chain.Address]*big.Int

blockCounter chain.BlockCounter
operatorPrivateKey *operator.PrivateKey
}
Expand Down Expand Up @@ -185,11 +191,26 @@ func (lc *localChain) setBlockHashByNumber(
}

func (lc *localChain) OperatorToStakingProvider() (chain.Address, bool, error) {
panic("unsupported")
return stakingProvider, true, nil
}

func (lc *localChain) EligibleStake(stakingProvider chain.Address) (*big.Int, error) {
panic("unsupported")
lc.eligibleStakesMutex.Lock()
defer lc.eligibleStakesMutex.Unlock()

eligibleStake, ok := lc.eligibleStakes[stakingProvider]
if !ok {
return nil, fmt.Errorf("eligible stake not found")
}

return eligibleStake, nil
}

func (lc *localChain) setOperatorsEligibleStake(stake *big.Int) {
lc.eligibleStakesMutex.Lock()
defer lc.eligibleStakesMutex.Unlock()

lc.eligibleStakes[stakingProvider] = stake
}

func (lc *localChain) IsPoolLocked() (bool, error) {
Expand All @@ -216,11 +237,6 @@ func (lc *localChain) IsEligibleForRewards() (bool, error) {
panic("unsupported")
}

func (lc *localChain) IsOperatorUnstaking() (bool, error) {
// TODO: Implement and use in unit tests.
return false, nil
}

func (lc *localChain) CanRestoreRewardEligibility() (bool, error) {
panic("unsupported")
}
Expand Down Expand Up @@ -1402,6 +1418,7 @@ func ConnectWithKey(
movedFundsSweepProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
depositRequests: make(map[[32]byte]*DepositChainRequest),
eligibleStakes: make(map[chain.Address]*big.Int),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
}
Expand Down
34 changes: 32 additions & 2 deletions pkg/tbtc/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,12 @@ func newHeartbeatAction(

func (ha *heartbeatAction) execute() error {
// Do not execute the heartbeat action if the operator is unstaking.
isUnstaking, err := ha.chain.IsOperatorUnstaking()
isUnstaking, err := ha.isOperatorUnstaking()
if err != nil {
return fmt.Errorf("failed to check if the operator is unstaking")
return fmt.Errorf(
"failed to check if the operator is unstaking [%v]",
err,
)
}

if isUnstaking {
Expand Down Expand Up @@ -247,6 +250,33 @@ func (ha *heartbeatAction) actionType() WalletActionType {
return ActionHeartbeat
}

func (ha *heartbeatAction) isOperatorUnstaking() (bool, error) {
stakingProvider, isRegistered, err := ha.chain.OperatorToStakingProvider()
if err != nil {
return false, fmt.Errorf(
"failed to get staking provider for operator [%v]",
err,
)
}

if !isRegistered {
return false, fmt.Errorf("staking provider not registered for operator")
}

// Eligible stake is defined as the currently authorized stake minus the
// pending authorization decrease.
eligibleStake, err := ha.chain.EligibleStake(stakingProvider)
if err != nil {
return false, fmt.Errorf(
"failed to check eligible stake for operator [%v]",
err,
)
}

// The operator is considered unstaking if their eligible stake is `0`.
return eligibleStake.Cmp(big.NewInt(0)) == 0, nil
}

// heartbeatFailureCounter holds counters keeping track of consecutive
// heartbeat failures. Each wallet has a separate counter. The key used in
// the map is the uncompressed public key (with 04 prefix) of the wallet.
Expand Down
66 changes: 66 additions & 0 deletions pkg/tbtc/heartbeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func TestHeartbeatAction_HappyPath(t *testing.T) {
}

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(100000))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

// Set the active operators count to the minimum required value.
Expand Down Expand Up @@ -102,6 +103,67 @@ func TestHeartbeatAction_HappyPath(t *testing.T) {
)
}

func TestHeartbeatAction_OperatorUnstaking(t *testing.T) {
walletPublicKeyHex, err := hex.DecodeString(
"0471e30bca60f6548d7b42582a478ea37ada63b402af7b3ddd57f0c95bb6843175" +
"aa0d2053a91a050a6797d85c38f2909cb7027f2344a01986aa2f9f8ca7a0c289",
)
if err != nil {
t.Fatal(err)
}

startBlock := uint64(10)
expiryBlock := startBlock + heartbeatTotalProposalValidityBlocks

proposal := &HeartbeatProposal{
Message: [16]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
},
}

heartbeatFailureCounter := newHeartbeatFailureCounter()

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(0))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

// Set the active operators count to the minimum required value.
mockExecutor := &mockHeartbeatSigningExecutor{}
mockExecutor.activeOperatorsCount = heartbeatSigningMinimumActiveOperators

inactivityClaimExecutor := &mockInactivityClaimExecutor{}

action := newHeartbeatAction(
logger,
hostChain,
wallet{
publicKey: unmarshalPublicKey(walletPublicKeyHex),
},
mockExecutor,
proposal,
heartbeatFailureCounter,
inactivityClaimExecutor,
startBlock,
expiryBlock,
func(ctx context.Context, blockHeight uint64) error {
return nil
},
)

err = action.execute()
if err != nil {
t.Fatal(err)
}

testutils.AssertBigIntsEqual(
t,
"message to sign",
nil, // sign not called
mockExecutor.requestedMessage,
)
}

func TestHeartbeatAction_Failure_SigningError(t *testing.T) {
walletPublicKeyHex, err := hex.DecodeString(
"0471e30bca60f6548d7b42582a478ea37ada63b402af7b3ddd57f0c95bb6843175" +
Expand All @@ -126,6 +188,7 @@ func TestHeartbeatAction_Failure_SigningError(t *testing.T) {
heartbeatFailureCounter := newHeartbeatFailureCounter()

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(100000))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

mockExecutor := &mockHeartbeatSigningExecutor{}
Expand Down Expand Up @@ -196,6 +259,7 @@ func TestHeartbeatAction_Failure_TooFewActiveOperators(t *testing.T) {
heartbeatFailureCounter := newHeartbeatFailureCounter()

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(100000))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

// Set the active operators count just below the required number.
Expand Down Expand Up @@ -276,6 +340,7 @@ func TestHeartbeatAction_Failure_CounterExceeded(t *testing.T) {
heartbeatFailureCounter.increment(walletPublicKeyStr)

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(100000))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

mockExecutor := &mockHeartbeatSigningExecutor{}
Expand Down Expand Up @@ -355,6 +420,7 @@ func TestHeartbeatAction_Failure_InactivityExecutionFailure(t *testing.T) {
heartbeatFailureCounter.increment(walletPublicKeyStr)

hostChain := Connect()
hostChain.setOperatorsEligibleStake(big.NewInt(100000))
hostChain.setHeartbeatProposalValidationResult(proposal, true)

mockExecutor := &mockHeartbeatSigningExecutor{}
Expand Down

0 comments on commit e5d61e7

Please sign in to comment.