Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 35b4353

Browse files
authored
Added support for the increase/decrease additional stake instructions in the Python client library (#6205)
* Added support for the increase/decrease additional stake instructions Following the JS implementation * fixed code style issues reported by CI/CD * code style fix * Removed duplicate reserve_stake member in DecreaseAdditionalValidatorStakeParams * stake_history_sysvar not needed for decrease additional validator stake * added stake history * added support for increase/decrease additional instruction using an optional ephemeral seed parameter * updated test to also test the new variations of the increase/decrease methods * updated test to also test the new variations of the increase/decrease methods * updated test to also test the new variations of the increase/decrease methods * fixes as suggested * fixed assert criteria * fixed assert criteria * fixed assert criteria * fixed assert criteria * added an epoch boundary between decrease validator and decrease additional validator * removed test for DecreaseAdditionalValidatorStake as the method does not work * nit fixing
1 parent d72289c commit 35b4353

File tree

4 files changed

+344
-52
lines changed

4 files changed

+344
-52
lines changed

stake-pool/py/stake_pool/actions.py

Lines changed: 140 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Tuple
1+
from typing import Optional, Tuple
22

33
from solana.keypair import Keypair
44
from solana.publickey import PublicKey
@@ -22,7 +22,8 @@
2222
find_stake_program_address, \
2323
find_transient_stake_program_address, \
2424
find_withdraw_authority_program_address, \
25-
find_metadata_account
25+
find_metadata_account, \
26+
find_ephemeral_stake_program_address
2627
from stake_pool.state import STAKE_POOL_LAYOUT, ValidatorList, Fee, StakePool
2728
import stake_pool.instructions as sp
2829

@@ -480,8 +481,13 @@ async def update_stake_pool(client: AsyncClient, payer: Keypair, stake_pool_addr
480481

481482

482483
async def increase_validator_stake(
483-
client: AsyncClient, payer: Keypair, staker: Keypair, stake_pool_address: PublicKey,
484-
validator_vote: PublicKey, lamports: int
484+
client: AsyncClient,
485+
payer: Keypair,
486+
staker: Keypair,
487+
stake_pool_address: PublicKey,
488+
validator_vote: PublicKey,
489+
lamports: int,
490+
ephemeral_stake_seed: Optional[int] = None
485491
):
486492
resp = await client.get_account_info(stake_pool_address, commitment=Confirmed)
487493
data = resp['result']['value']['data']
@@ -493,7 +499,13 @@ async def increase_validator_stake(
493499
(withdraw_authority, seed) = find_withdraw_authority_program_address(STAKE_POOL_PROGRAM_ID, stake_pool_address)
494500

495501
validator_info = next(x for x in validator_list.validators if x.vote_account_address == validator_vote)
496-
transient_stake_seed = validator_info.transient_seed_suffix + 1 # bump up by one to avoid reuse
502+
503+
if ephemeral_stake_seed is None:
504+
transient_stake_seed = validator_info.transient_seed_suffix + 1 # bump up by one to avoid reuse
505+
else:
506+
# we are updating an existing transient stake account, so we must use the same seed
507+
transient_stake_seed = validator_info.transient_seed_suffix
508+
497509
validator_stake_seed = validator_info.validator_seed_suffix or None
498510
(transient_stake, _) = find_transient_stake_program_address(
499511
STAKE_POOL_PROGRAM_ID,
@@ -509,38 +521,78 @@ async def increase_validator_stake(
509521
)
510522

511523
txn = Transaction()
512-
txn.add(
513-
sp.increase_validator_stake(
514-
sp.IncreaseValidatorStakeParams(
515-
program_id=STAKE_POOL_PROGRAM_ID,
516-
stake_pool=stake_pool_address,
517-
staker=staker.public_key,
518-
withdraw_authority=withdraw_authority,
519-
validator_list=stake_pool.validator_list,
520-
reserve_stake=stake_pool.reserve_stake,
521-
transient_stake=transient_stake,
522-
validator_stake=validator_stake,
523-
validator_vote=validator_vote,
524-
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
525-
rent_sysvar=SYSVAR_RENT_PUBKEY,
526-
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
527-
stake_config_sysvar=SYSVAR_STAKE_CONFIG_ID,
528-
system_program_id=sys.SYS_PROGRAM_ID,
529-
stake_program_id=STAKE_PROGRAM_ID,
530-
lamports=lamports,
531-
transient_stake_seed=transient_stake_seed,
524+
if ephemeral_stake_seed is not None:
525+
526+
# We assume there is an existing transient account that we will update
527+
(ephemeral_stake, _) = find_ephemeral_stake_program_address(
528+
STAKE_POOL_PROGRAM_ID,
529+
stake_pool_address,
530+
ephemeral_stake_seed)
531+
532+
txn.add(
533+
sp.increase_additional_validator_stake(
534+
sp.IncreaseAdditionalValidatorStakeParams(
535+
program_id=STAKE_POOL_PROGRAM_ID,
536+
stake_pool=stake_pool_address,
537+
staker=staker.public_key,
538+
withdraw_authority=withdraw_authority,
539+
validator_list=stake_pool.validator_list,
540+
reserve_stake=stake_pool.reserve_stake,
541+
transient_stake=transient_stake,
542+
validator_stake=validator_stake,
543+
validator_vote=validator_vote,
544+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
545+
rent_sysvar=SYSVAR_RENT_PUBKEY,
546+
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
547+
stake_config_sysvar=SYSVAR_STAKE_CONFIG_ID,
548+
system_program_id=sys.SYS_PROGRAM_ID,
549+
stake_program_id=STAKE_PROGRAM_ID,
550+
lamports=lamports,
551+
transient_stake_seed=transient_stake_seed,
552+
ephemeral_stake=ephemeral_stake,
553+
ephemeral_stake_seed=ephemeral_stake_seed
554+
)
555+
)
556+
)
557+
558+
else:
559+
txn.add(
560+
sp.increase_validator_stake(
561+
sp.IncreaseValidatorStakeParams(
562+
program_id=STAKE_POOL_PROGRAM_ID,
563+
stake_pool=stake_pool_address,
564+
staker=staker.public_key,
565+
withdraw_authority=withdraw_authority,
566+
validator_list=stake_pool.validator_list,
567+
reserve_stake=stake_pool.reserve_stake,
568+
transient_stake=transient_stake,
569+
validator_stake=validator_stake,
570+
validator_vote=validator_vote,
571+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
572+
rent_sysvar=SYSVAR_RENT_PUBKEY,
573+
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
574+
stake_config_sysvar=SYSVAR_STAKE_CONFIG_ID,
575+
system_program_id=sys.SYS_PROGRAM_ID,
576+
stake_program_id=STAKE_PROGRAM_ID,
577+
lamports=lamports,
578+
transient_stake_seed=transient_stake_seed,
579+
)
532580
)
533581
)
534-
)
535582

536583
signers = [payer, staker] if payer != staker else [payer]
537584
await client.send_transaction(
538585
txn, *signers, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
539586

540587

541588
async def decrease_validator_stake(
542-
client: AsyncClient, payer: Keypair, staker: Keypair, stake_pool_address: PublicKey,
543-
validator_vote: PublicKey, lamports: int
589+
client: AsyncClient,
590+
payer: Keypair,
591+
staker: Keypair,
592+
stake_pool_address: PublicKey,
593+
validator_vote: PublicKey,
594+
lamports: int,
595+
ephemeral_stake_seed: Optional[int] = None
544596
):
545597
resp = await client.get_account_info(stake_pool_address, commitment=Confirmed)
546598
data = resp['result']['value']['data']
@@ -559,7 +611,13 @@ async def decrease_validator_stake(
559611
stake_pool_address,
560612
validator_stake_seed,
561613
)
562-
transient_stake_seed = validator_info.transient_seed_suffix + 1 # bump up by one to avoid reuse
614+
615+
if ephemeral_stake_seed is None:
616+
transient_stake_seed = validator_info.transient_seed_suffix + 1 # bump up by one to avoid reuse
617+
else:
618+
# we are updating an existing transient stake account, so we must use the same seed
619+
transient_stake_seed = validator_info.transient_seed_suffix
620+
563621
(transient_stake, _) = find_transient_stake_program_address(
564622
STAKE_POOL_PROGRAM_ID,
565623
validator_info.vote_account_address,
@@ -568,26 +626,61 @@ async def decrease_validator_stake(
568626
)
569627

570628
txn = Transaction()
571-
txn.add(
572-
sp.decrease_validator_stake_with_reserve(
573-
sp.DecreaseValidatorStakeWithReserveParams(
574-
program_id=STAKE_POOL_PROGRAM_ID,
575-
stake_pool=stake_pool_address,
576-
staker=staker.public_key,
577-
withdraw_authority=withdraw_authority,
578-
validator_list=stake_pool.validator_list,
579-
reserve_stake=stake_pool.reserve_stake,
580-
validator_stake=validator_stake,
581-
transient_stake=transient_stake,
582-
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
583-
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
584-
system_program_id=sys.SYS_PROGRAM_ID,
585-
stake_program_id=STAKE_PROGRAM_ID,
586-
lamports=lamports,
587-
transient_stake_seed=transient_stake_seed,
629+
630+
if ephemeral_stake_seed is not None:
631+
632+
# We assume there is an existing transient account that we will update
633+
(ephemeral_stake, _) = find_ephemeral_stake_program_address(
634+
STAKE_POOL_PROGRAM_ID,
635+
stake_pool_address,
636+
ephemeral_stake_seed)
637+
638+
txn.add(
639+
sp.decrease_additional_validator_stake(
640+
sp.DecreaseAdditionalValidatorStakeParams(
641+
program_id=STAKE_POOL_PROGRAM_ID,
642+
stake_pool=stake_pool_address,
643+
staker=staker.public_key,
644+
withdraw_authority=withdraw_authority,
645+
validator_list=stake_pool.validator_list,
646+
reserve_stake=stake_pool.reserve_stake,
647+
validator_stake=validator_stake,
648+
transient_stake=transient_stake,
649+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
650+
rent_sysvar=SYSVAR_RENT_PUBKEY,
651+
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
652+
system_program_id=sys.SYS_PROGRAM_ID,
653+
stake_program_id=STAKE_PROGRAM_ID,
654+
lamports=lamports,
655+
transient_stake_seed=transient_stake_seed,
656+
ephemeral_stake=ephemeral_stake,
657+
ephemeral_stake_seed=ephemeral_stake_seed
658+
)
659+
)
660+
)
661+
662+
else:
663+
664+
txn.add(
665+
sp.decrease_validator_stake_with_reserve(
666+
sp.DecreaseValidatorStakeWithReserveParams(
667+
program_id=STAKE_POOL_PROGRAM_ID,
668+
stake_pool=stake_pool_address,
669+
staker=staker.public_key,
670+
withdraw_authority=withdraw_authority,
671+
validator_list=stake_pool.validator_list,
672+
reserve_stake=stake_pool.reserve_stake,
673+
validator_stake=validator_stake,
674+
transient_stake=transient_stake,
675+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
676+
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
677+
system_program_id=sys.SYS_PROGRAM_ID,
678+
stake_program_id=STAKE_PROGRAM_ID,
679+
lamports=lamports,
680+
transient_stake_seed=transient_stake_seed,
681+
)
588682
)
589683
)
590-
)
591684

592685
signers = [payer, staker] if payer != staker else [payer]
593686
await client.send_transaction(

stake-pool/py/stake_pool/constants.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ def find_transient_stake_program_address(
7878
)
7979

8080

81+
def find_ephemeral_stake_program_address(
82+
program_id: PublicKey,
83+
stake_pool_address: PublicKey,
84+
seed: int
85+
) -> Tuple[PublicKey, int]:
86+
87+
"""Generates the ephemeral program address for stake pool redelegation"""
88+
return PublicKey.find_program_address(
89+
[
90+
EPHEMERAL_STAKE_SEED_PREFIX,
91+
bytes(stake_pool_address),
92+
seed.to_bytes(8, 'little'),
93+
],
94+
program_id,
95+
)
96+
97+
8198
def find_metadata_account(
8299
mint_key: PublicKey
83100
) -> Tuple[PublicKey, int]:
@@ -100,3 +117,5 @@ def find_metadata_account(
100117
"""Seed used to derive transient stake accounts."""
101118
METADATA_SEED_PREFIX = b"metadata"
102119
"""Seed used to avoid certain collision attacks."""
120+
EPHEMERAL_STAKE_SEED_PREFIX = b'ephemeral'
121+
"""Seed for ephemeral stake account"""

0 commit comments

Comments
 (0)