Skip to content

Commit

Permalink
[JIT-519] Store ClaimStatus address in merkle-root-json (#210)
Browse files Browse the repository at this point in the history
* add files

* switch to include bump
  • Loading branch information
esemeniuc authored and buffalu committed Feb 10, 2023
1 parent 9796295 commit 694ff2f
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 239 deletions.
13 changes: 0 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions sdk/program/src/stake/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ impl Default for StakeState {
}

impl StakeState {
/// The fixed number of bytes used to serialize each stake account
pub const fn size_of() -> usize {
200
}

pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
rent.minimum_balance(std::mem::size_of::<StakeState>())
}
Expand Down
2 changes: 0 additions & 2 deletions tip-distributor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ description = "Collection of binaries used to distribute MEV rewards to delegato

[dependencies]
anchor-lang = { path = "../anchor/lang" }
bigdecimal = "0.3.0"
clap = { version = "3.2.5", features = ["derive", "env"] }
env_logger = "0.9.0"
futures = "0.3.21"
Expand All @@ -22,7 +21,6 @@ solana-genesis-utils = { path = "../genesis-utils", version = "=1.13.7" }
solana-ledger = { path = "../ledger", version = "=1.13.7" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.13.7" }
solana-program = { path = "../sdk/program", version = "=1.13.7" }
solana-rpc = { path = "../rpc", version = "=1.13.7" }
solana-runtime = { path = "../runtime", version = "=1.13.7" }
solana-sdk = { path = "../sdk", version = "=1.13.7" }
solana-stake-program = { path = "../programs/stake", version = "=1.13.7" }
Expand Down
2 changes: 1 addition & 1 deletion tip-distributor/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tip Distributor
This library and collection of binaries are responsible for generating and uploading merkle roots to the on-chain
tip-distribution program found [here](https://github.com/jito-labs/jito-programs/blob/a450ef006e60e10894c02269ec8a301b81a083a0/tip-payment/programs/tip-distribution/src/lib.rs).
tip-distribution program found [here](https://github.com/jito-foundation/jito-programs/blob/submodule/tip-payment/programs/tip-distribution/src/lib.rs).

## Background
Each individual validator is assigned a new PDA per epoch where their share of tips, in lamports, will be stored.
Expand Down
157 changes: 81 additions & 76 deletions tip-distributor/src/claim_mev_workflow.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use {
crate::{read_json_from_file, send_transactions_with_retry, GeneratedMerkleTreeCollection},
anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas},
log::{debug, info},
solana_client::{
client_error::ClientErrorKind, nonblocking::rpc_client::RpcClient, rpc_request::RpcError,
log::{debug, info, warn},
solana_client::{nonblocking::rpc_client::RpcClient, rpc_request::RpcError},
solana_program::{
fee_calculator::DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE, native_token::LAMPORTS_PER_SOL,
stake::state::StakeState, system_program,
},
solana_program::system_program,
solana_sdk::{
commitment_config::CommitmentConfig,
instruction::Instruction,
Expand Down Expand Up @@ -56,86 +57,90 @@ pub fn claim_mev_tips(
let mut transactions = Vec::new();

runtime.block_on(async move {
let blockhash = rpc_client
.get_latest_blockhash()
.await
.expect("read blockhash");

let blockhash = rpc_client.get_latest_blockhash().await.expect("read blockhash");
let start_balance = rpc_client.get_balance(&keypair.pubkey()).await.expect("failed to get balance");
// heuristic to make sure we have enough funds to cover the rent costs if epoch has many validators
{
// most amounts are for 0 lamports. had 1736 non-zero claims out of 164742
let node_count = merkle_trees.generated_merkle_trees.iter().flat_map(|tree| &tree.tree_nodes).filter(|node| node.amount > 0).count();
let min_rent_per_claim = rpc_client.get_minimum_balance_for_rent_exemption(ClaimStatus::SIZE).await.expect("Failed to calculate min rent");
let desired_balance = (node_count as u64).checked_mul(min_rent_per_claim.checked_add(DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE).unwrap()).unwrap();
if start_balance < desired_balance {
let sol_to_deposit = desired_balance.checked_sub(start_balance).unwrap().checked_add(LAMPORTS_PER_SOL).unwrap().checked_sub(1).unwrap().checked_div(LAMPORTS_PER_SOL).unwrap(); // rounds up to nearest sol
panic!("Expected to have at least {} lamports in {}, current balance is {} lamports, deposit {} SOL to continue.",
desired_balance, &keypair.pubkey(), start_balance, sol_to_deposit)
}
}
let stake_acct_min_rent = rpc_client.get_minimum_balance_for_rent_exemption(StakeState::size_of()).await.expect("Failed to calculate min rent");
let mut below_min_rent_count: usize = 0;
let mut zero_lamports_count: usize = 0;
for tree in merkle_trees.generated_merkle_trees {
// only claim for ones that have merkle root on-chain
let account = rpc_client.get_account(&tree.tip_distribution_account).await.expect("expected to fetch tip distribution account");
let fetched_tip_distribution_account = TipDistributionAccount::try_deserialize(&mut account.data.as_slice()).expect("failed to deserialize tip_distribution_account state");
if fetched_tip_distribution_account.merkle_root.is_none() {
info!(
"not claiming because merkle root isn't uploaded yet. skipped {} claimants for tda: {:?}",
tree.tree_nodes.len(),
tree.tip_distribution_account
);
continue;
}
for node in tree.tree_nodes {
let (claim_status_pubkey, claim_status_bump) = Pubkey::find_program_address(
&[
ClaimStatus::SEED,
node.claimant.as_ref(), // ordering matters here
tree.tip_distribution_account.as_ref(),
],
tip_distribution_program_id,
);

// only claim for ones that have merkle root on-chain
let account = rpc_client
.get_account(&tree.tip_distribution_account)
.await
.expect("expected to fetch tip distribution account");

let mut data = account.data.as_slice();
let fetched_tip_distribution_account =
TipDistributionAccount::try_deserialize(&mut data)
.expect("failed to deserialize tip_distribution_account state");

if fetched_tip_distribution_account.merkle_root.is_some() {
match rpc_client.get_account(&claim_status_pubkey).await {
Ok(_) => {
debug!(
"claim status account already exists: {:?}",
claim_status_pubkey
);
}
Err(e) => {
if matches!(e.kind(), ClientErrorKind::RpcError(RpcError::ForUser(_))) {
info!("claiming for public key: {:?}", node.claimant);
if node.amount == 0 {
zero_lamports_count = zero_lamports_count.checked_add(1).unwrap();
continue;
}

let ix = Instruction {
program_id: *tip_distribution_program_id,
data: tip_distribution::instruction::Claim {
proof: node.proof.unwrap(),
amount: node.amount,
bump: claim_status_bump,
}
.data(),
accounts: tip_distribution::accounts::Claim {
config: tip_distribution_config,
tip_distribution_account: tree.tip_distribution_account,
claimant: node.claimant,
claim_status: claim_status_pubkey,
payer: keypair.pubkey(),
system_program: system_program::id(),
}
.to_account_metas(None),
};
let transaction = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair.pubkey()),
&[&keypair],
blockhash,
);
info!("tx: {:?}", transaction);
transactions.push(transaction);
} else {
panic!("unexpected rpc error: {:?}", e);
}
}
// make sure not previously claimed
match rpc_client.get_account(&node.claim_status_pubkey).await {
Ok(_) => {
debug!("claim status account already exists, skipping pubkey {:?}.", node.claim_status_pubkey);
continue;
}
} else {
info!(
"not claiming because merkle root isn't uploaded yet claimant: {:?} tda: {:?}",
node.claimant,
tree.tip_distribution_account
);
// expected to not find ClaimStatus account, don't skip
Err(solana_client::client_error::ClientError { kind: solana_client::client_error::ClientErrorKind::RpcError(RpcError::ForUser(err)), .. }) if err.starts_with("AccountNotFound") => {}
Err(err) => panic!("Unexpected RPC Error: {}", err),
}

let current_balance = rpc_client.get_balance(&node.claimant).await.expect("Failed to get balance");
// some older accounts can be rent-paying
// any new transfers will need to make the account rent-exempt (runtime enforced)
if current_balance.checked_add(node.amount).unwrap() < stake_acct_min_rent {
warn!("Current balance + tip claim amount of {} is less than required rent-exempt of {} for pubkey: {}. Skipping.",
current_balance.checked_add(node.amount).unwrap(), stake_acct_min_rent, node.claimant);
below_min_rent_count = below_min_rent_count.checked_add(1).unwrap();
continue;
}
let ix = Instruction {
program_id: *tip_distribution_program_id,
data: tip_distribution::instruction::Claim {
proof: node.proof.unwrap(),
amount: node.amount,
bump: node.claim_status_bump,
}.data(),
accounts: tip_distribution::accounts::Claim {
config: tip_distribution_config,
tip_distribution_account: tree.tip_distribution_account,
claimant: node.claimant,
claim_status: node.claim_status_pubkey,
payer: keypair.pubkey(),
system_program: system_program::id(),
}.to_account_metas(None),
};
let transaction = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair.pubkey()),
&[&keypair],
blockhash,
);
info!("claiming for pubkey: {}, tx: {:?}", node.claimant, transaction);
transactions.push(transaction);
}
}

info!("Sending {} tip claim transactions. {} tried sending zero lamports, {} would be below minimum rent",
&transactions.len(), zero_lamports_count, below_min_rent_count);
send_transactions_with_retry(&rpc_client, &transactions, MAX_RETRY_DURATION).await;
});

Expand Down
Loading

0 comments on commit 694ff2f

Please sign in to comment.