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

Commit 72aeb30

Browse files
committed
Add ability to add / remove validators
1 parent 16fdfd3 commit 72aeb30

File tree

9 files changed

+379
-21
lines changed

9 files changed

+379
-21
lines changed

ci/py-test-stake-pool.sh

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ check_dirs=(
1313
"stake"
1414
"stake_pool"
1515
"tests"
16+
"vote"
1617
)
1718
flake8 "${check_dirs[@]}"
1819
mypy "${check_dirs[@]}"

stake-pool/py/actions/stake_pool.py

+99-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Tuple
2+
13
from solana.keypair import Keypair
24
from solana.publickey import PublicKey
35
from solana.rpc.async_api import AsyncClient
@@ -8,16 +10,19 @@
810

911
from spl.token.constants import TOKEN_PROGRAM_ID
1012

11-
from stake_pool.constants import STAKE_POOL_PROGRAM_ID
12-
from stake_pool.state import STAKE_POOL_LAYOUT, calculate_validator_list_size, Fee
13+
from stake.constants import STAKE_PROGRAM_ID, STAKE_LEN
14+
from stake_pool.constants import STAKE_POOL_PROGRAM_ID, find_withdraw_authority_program_address
15+
from stake_pool.state import STAKE_POOL_LAYOUT, ValidatorList, Fee, StakePool
1316
import stake_pool.instructions as sp
1417

18+
import actions.stake
19+
import actions.token
20+
1521

1622
async def create(client: AsyncClient, manager: Keypair,
1723
stake_pool: Keypair, validator_list: Keypair,
1824
pool_mint: PublicKey, reserve_stake: PublicKey,
1925
manager_fee_account: PublicKey, fee: Fee, referral_fee: int):
20-
print(f"Creating stake pool {stake_pool.public_key}")
2126
resp = await client.get_minimum_balance_for_rent_exemption(STAKE_POOL_LAYOUT.sizeof())
2227
pool_balance = resp['result']
2328
txn = Transaction()
@@ -32,9 +37,8 @@ async def create(client: AsyncClient, manager: Keypair,
3237
)
3338
)
3439
)
35-
print(f"Creating validator list {validator_list.public_key}")
3640
max_validators = 3950 # current supported max by the program, go big!
37-
validator_list_size = calculate_validator_list_size(max_validators)
41+
validator_list_size = ValidatorList.calculate_validator_list_size(max_validators)
3842
resp = await client.get_minimum_balance_for_rent_exemption(validator_list_size)
3943
validator_list_balance = resp['result']
4044
txn.add(
@@ -51,7 +55,6 @@ async def create(client: AsyncClient, manager: Keypair,
5155
await client.send_transaction(
5256
txn, manager, stake_pool, validator_list, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
5357

54-
print("Initializing stake pool...")
5558
txn = Transaction()
5659
txn.add(
5760
sp.initialize(
@@ -75,3 +78,93 @@ async def create(client: AsyncClient, manager: Keypair,
7578
)
7679
await client.send_transaction(
7780
txn, manager, validator_list, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
81+
82+
83+
async def create_all(client: AsyncClient, manager: Keypair, fee: Fee, referral_fee: int) -> Tuple[PublicKey, PublicKey]:
84+
stake_pool = Keypair()
85+
validator_list = Keypair()
86+
(pool_withdraw_authority, seed) = find_withdraw_authority_program_address(
87+
STAKE_POOL_PROGRAM_ID, stake_pool.public_key)
88+
89+
reserve_stake = Keypair()
90+
await actions.stake.create_stake(client, manager, reserve_stake, pool_withdraw_authority)
91+
92+
pool_mint = Keypair()
93+
await actions.token.create_mint(client, manager, pool_mint, pool_withdraw_authority)
94+
95+
manager_fee_account = await actions.token.create_associated_token_account(
96+
client,
97+
manager,
98+
manager.public_key,
99+
pool_mint.public_key,
100+
)
101+
102+
fee = Fee(numerator=1, denominator=1000)
103+
referral_fee = 20
104+
await create(
105+
client, manager, stake_pool, validator_list, pool_mint.public_key,
106+
reserve_stake.public_key, manager_fee_account, fee, referral_fee)
107+
return (stake_pool.public_key, validator_list.public_key)
108+
109+
110+
async def add_validator_to_pool(
111+
client: AsyncClient, funder: Keypair,
112+
stake_pool_address: PublicKey, validator: PublicKey
113+
):
114+
resp = await client.get_account_info(stake_pool_address, commitment=Confirmed)
115+
data = resp['result']['value']['data']
116+
stake_pool = StakePool.decode(data[0], data[1])
117+
txn = Transaction()
118+
txn.add(
119+
sp.add_validator_to_pool_with_vote(
120+
STAKE_POOL_PROGRAM_ID,
121+
stake_pool_address,
122+
stake_pool.staker,
123+
stake_pool.validator_list,
124+
funder.public_key,
125+
validator,
126+
)
127+
)
128+
await client.send_transaction(
129+
txn, funder, opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))
130+
131+
132+
async def remove_validator_from_pool(
133+
client: AsyncClient, staker: Keypair,
134+
stake_pool_address: PublicKey, validator: PublicKey
135+
):
136+
resp = await client.get_account_info(stake_pool_address, commitment=Confirmed)
137+
data = resp['result']['value']['data']
138+
stake_pool = StakePool.decode(data[0], data[1])
139+
resp = await client.get_account_info(stake_pool.validator_list, commitment=Confirmed)
140+
data = resp['result']['value']['data']
141+
validator_list = ValidatorList.decode(data[0], data[1])
142+
validator_info = next(x for x in validator_list.validators if x.vote_account_address == validator)
143+
destination_stake = Keypair()
144+
txn = Transaction()
145+
txn.add(
146+
sys.create_account(
147+
sys.CreateAccountParams(
148+
from_pubkey=staker.public_key,
149+
new_account_pubkey=destination_stake.public_key,
150+
lamports=0, # will get filled by split
151+
space=STAKE_LEN,
152+
program_id=STAKE_PROGRAM_ID,
153+
)
154+
)
155+
)
156+
txn.add(
157+
sp.remove_validator_from_pool_with_vote(
158+
STAKE_POOL_PROGRAM_ID,
159+
stake_pool_address,
160+
stake_pool.staker,
161+
stake_pool.validator_list,
162+
staker.public_key,
163+
validator,
164+
validator_info.transient_seed_suffix_start,
165+
destination_stake.public_key
166+
)
167+
)
168+
await client.send_transaction(
169+
txn, staker, destination_stake,
170+
opts=TxOpts(skip_confirmation=False, preflight_commitment=Confirmed))

stake-pool/py/stake/constants.py

+3
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@
55
STAKE_PROGRAM_ID: PublicKey = PublicKey("Stake11111111111111111111111111111111111111")
66
"""Public key that identifies the Stake program."""
77

8+
SYSVAR_STAKE_CONFIG_ID: PublicKey = PublicKey("StakeConfig11111111111111111111111111111111")
9+
"""Public key that identifies the Stake config sysvar."""
10+
811
STAKE_LEN: int = 200
912
"""Size of stake account."""

stake-pool/py/stake_pool/instructions.py

+118
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66

77
from solana.publickey import PublicKey
88
from solana.transaction import AccountMeta, TransactionInstruction
9+
from solana.system_program import SYS_PROGRAM_ID
10+
from solana.sysvar import SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY
911
from spl.token.constants import TOKEN_PROGRAM_ID
1012

13+
from stake.constants import STAKE_PROGRAM_ID, SYSVAR_STAKE_CONFIG_ID
14+
from stake_pool.constants import find_stake_program_address, find_transient_stake_program_address
15+
from stake_pool.constants import find_withdraw_authority_program_address
16+
from stake_pool.constants import STAKE_POOL_PROGRAM_ID
1117
from stake_pool.state import Fee, FEE_LAYOUT
1218

1319

@@ -478,3 +484,115 @@ def initialize(params: InitializeParams) -> TransactionInstruction:
478484
program_id=params.program_id,
479485
data=data,
480486
)
487+
488+
489+
def add_validator_to_pool(params: AddValidatorToPoolParams) -> TransactionInstruction:
490+
return TransactionInstruction(
491+
keys=[
492+
AccountMeta(pubkey=params.stake_pool, is_signer=False, is_writable=False),
493+
AccountMeta(pubkey=params.staker, is_signer=True, is_writable=False),
494+
AccountMeta(pubkey=params.funding_account, is_signer=True, is_writable=True),
495+
AccountMeta(pubkey=params.withdraw_authority, is_signer=False, is_writable=False),
496+
AccountMeta(pubkey=params.validator_list, is_signer=False, is_writable=True),
497+
AccountMeta(pubkey=params.validator_stake, is_signer=False, is_writable=True),
498+
AccountMeta(pubkey=params.validator_vote, is_signer=False, is_writable=False),
499+
AccountMeta(pubkey=params.rent_sysvar, is_signer=False, is_writable=False),
500+
AccountMeta(pubkey=params.clock_sysvar, is_signer=False, is_writable=False),
501+
AccountMeta(pubkey=params.stake_history_sysvar, is_signer=False, is_writable=False),
502+
AccountMeta(pubkey=params.stake_config_sysvar, is_signer=False, is_writable=False),
503+
AccountMeta(pubkey=params.system_program_id, is_signer=False, is_writable=False),
504+
AccountMeta(pubkey=params.stake_program_id, is_signer=False, is_writable=False),
505+
],
506+
program_id=params.program_id,
507+
data=INSTRUCTIONS_LAYOUT.build(
508+
dict(
509+
instruction_type=InstructionType.ADD_VALIDATOR_TO_POOL,
510+
args=None
511+
)
512+
)
513+
)
514+
515+
516+
def add_validator_to_pool_with_vote(
517+
program_id: PublicKey,
518+
stake_pool: PublicKey,
519+
staker: PublicKey,
520+
validator_list: PublicKey,
521+
funder: PublicKey,
522+
validator: PublicKey
523+
) -> TransactionInstruction:
524+
(withdraw_authority, seed) = find_withdraw_authority_program_address(program_id, stake_pool)
525+
(validator_stake, seed) = find_stake_program_address(program_id, validator, stake_pool)
526+
return add_validator_to_pool(
527+
AddValidatorToPoolParams(
528+
program_id=STAKE_POOL_PROGRAM_ID,
529+
stake_pool=stake_pool,
530+
staker=staker,
531+
funding_account=funder,
532+
withdraw_authority=withdraw_authority,
533+
validator_list=validator_list,
534+
validator_stake=validator_stake,
535+
validator_vote=validator,
536+
rent_sysvar=SYSVAR_RENT_PUBKEY,
537+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
538+
stake_history_sysvar=SYSVAR_STAKE_HISTORY_PUBKEY,
539+
stake_config_sysvar=SYSVAR_STAKE_CONFIG_ID,
540+
system_program_id=SYS_PROGRAM_ID,
541+
stake_program_id=STAKE_PROGRAM_ID,
542+
)
543+
)
544+
545+
546+
def remove_validator_from_pool(params: RemoveValidatorFromPoolParams) -> TransactionInstruction:
547+
return TransactionInstruction(
548+
keys=[
549+
AccountMeta(pubkey=params.stake_pool, is_signer=False, is_writable=False),
550+
AccountMeta(pubkey=params.staker, is_signer=True, is_writable=False),
551+
AccountMeta(pubkey=params.withdraw_authority, is_signer=False, is_writable=False),
552+
AccountMeta(pubkey=params.new_stake_authority, is_signer=False, is_writable=True),
553+
AccountMeta(pubkey=params.validator_list, is_signer=False, is_writable=True),
554+
AccountMeta(pubkey=params.validator_stake, is_signer=False, is_writable=True),
555+
AccountMeta(pubkey=params.transient_stake, is_signer=False, is_writable=False),
556+
AccountMeta(pubkey=params.destination_stake, is_signer=False, is_writable=True),
557+
AccountMeta(pubkey=params.clock_sysvar, is_signer=False, is_writable=False),
558+
AccountMeta(pubkey=params.stake_program_id, is_signer=False, is_writable=False),
559+
],
560+
program_id=params.program_id,
561+
data=INSTRUCTIONS_LAYOUT.build(
562+
dict(
563+
instruction_type=InstructionType.REMOVE_VALIDATOR_FROM_POOL,
564+
args=None
565+
)
566+
)
567+
)
568+
569+
570+
def remove_validator_from_pool_with_vote(
571+
program_id: PublicKey,
572+
stake_pool: PublicKey,
573+
staker: PublicKey,
574+
validator_list: PublicKey,
575+
new_stake_authority: PublicKey,
576+
validator: PublicKey,
577+
transient_stake_seed: int,
578+
destination_stake: PublicKey,
579+
) -> TransactionInstruction:
580+
(withdraw_authority, seed) = find_withdraw_authority_program_address(program_id, stake_pool)
581+
(validator_stake, seed) = find_stake_program_address(program_id, validator, stake_pool)
582+
(transient_stake, seed) = find_transient_stake_program_address(
583+
program_id, validator, stake_pool, transient_stake_seed)
584+
return remove_validator_from_pool(
585+
RemoveValidatorFromPoolParams(
586+
program_id=STAKE_POOL_PROGRAM_ID,
587+
stake_pool=stake_pool,
588+
staker=staker,
589+
withdraw_authority=withdraw_authority,
590+
new_stake_authority=new_stake_authority,
591+
validator_list=validator_list,
592+
validator_stake=validator_stake,
593+
transient_stake=transient_stake,
594+
destination_stake=destination_stake,
595+
clock_sysvar=SYSVAR_CLOCK_PUBKEY,
596+
stake_program_id=STAKE_PROGRAM_ID,
597+
)
598+
)

0 commit comments

Comments
 (0)