Skip to content

Commit 37d6339

Browse files
authored
feat: optionally reclaim expired account rent from operator (#172)
**Problem** Our rent reclamation is currently an ad-hoc script. The tip router operator should handle operations like these without a need for manual action. **Solution** - Add tx_utils for common utils for sending **bulk** transactions - Add reclaim mod with the expired claim status account related functions - Add expired distribution account related functions - Use num_monitored_epochs as bounds for epochs to check for expired accounts - Call reclaim expired accounts from main.rs Run - Emit metrics related to expired transactions <img width="835" height="290" alt="Screenshot 2025-07-23 at 1 53 03 PM" src="https://github.com/user-attachments/assets/acebe38f-8b09-4be3-a0ac-8a0a05c42409" /> <img width="832" height="278" alt="Screenshot 2025-07-24 at 12 05 02 PM" src="https://github.com/user-attachments/assets/373b6037-fec2-459b-9405-f58c508e47b5" />
1 parent a2390ef commit 37d6339

File tree

11 files changed

+703
-24
lines changed

11 files changed

+703
-24
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ jobs:
1818
runs-on: ubuntu-latest
1919
steps:
2020
- uses: actions/checkout@v4
21-
with:
22-
submodules: recursive
2321
- name: Install cargo-audit from crates.io
2422
uses: baptiste0928/cargo-install@v3
2523
with:
@@ -32,8 +30,6 @@ jobs:
3230
runs-on: ubuntu-latest
3331
steps:
3432
- uses: actions/checkout@v4
35-
with:
36-
submodules: recursive
3733
- uses: actions-rust-lang/setup-rust-toolchain@v1
3834
with:
3935
components: rustfmt, clippy
@@ -74,8 +70,6 @@ jobs:
7470
runs-on: ubuntu-latest
7571
steps:
7672
- uses: actions/checkout@v4
77-
with:
78-
submodules: recursive
7973
- name: Install Protobuf Compiler
8074
run: |
8175
sudo apt-get update
@@ -136,8 +130,6 @@ jobs:
136130
runs-on: big-runner-1
137131
steps:
138132
- uses: actions/checkout@v4
139-
with:
140-
submodules: recursive
141133
- name: Install system dependencies
142134
run: sudo apt-get update && sudo apt-get install -y libudev-dev
143135
- run: docker pull --platform linux/amd64 solanafoundation/solana-verifiable-build:2.2.14

priority_fee_distribution_sdk/src/instruction.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,32 @@ pub fn close_claim_status_ix(
140140
}
141141
}
142142

143+
pub fn close_priority_fee_distribution_account_ix(
144+
config: Pubkey,
145+
priority_fee_distribution_account: Pubkey,
146+
expired_funds_account: Pubkey,
147+
validator_vote_account: Pubkey,
148+
signer: Pubkey,
149+
epoch: u64,
150+
) -> Instruction {
151+
Instruction {
152+
program_id: jito_priority_fee_distribution::ID,
153+
accounts:
154+
jito_priority_fee_distribution::client::accounts::ClosePriorityFeeDistributionAccount {
155+
config,
156+
priority_fee_distribution_account,
157+
expired_funds_account,
158+
validator_vote_account,
159+
signer,
160+
}
161+
.to_account_metas(None),
162+
data: jito_priority_fee_distribution::client::args::ClosePriorityFeeDistributionAccount {
163+
_epoch: epoch,
164+
}
165+
.data(),
166+
}
167+
}
168+
143169
pub fn migrate_tda_merkle_root_upload_authority_ix(
144170
priority_fee_distribution_account: Pubkey,
145171
merkle_root_upload_config: Pubkey,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit b38bfedee0826d351776dd5597c96c263b28cbeb

tip-router-operator-cli/src/cli.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ pub enum Commands {
172172
#[arg(long, env, default_value = "true")]
173173
save_stages: bool,
174174

175+
#[arg(long, env, default_value = "false")]
176+
reclaim_expired_accounts: bool,
177+
175178
#[arg(
176179
long,
177180
env,

tip-router-operator-cli/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ pub mod distribution_meta;
1111
pub mod load_and_process_ledger;
1212
pub mod priority_fees;
1313
pub mod process_epoch;
14+
pub mod reclaim;
1415
pub mod rpc_utils;
1516
pub mod solana_cli;
1617
pub mod submit;
18+
pub mod tx_utils;
1719

1820
use std::fs;
1921
use std::path::{Path, PathBuf};

tip-router-operator-cli/src/main.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ::{
1515
create_merkle_tree_collection, create_meta_merkle_tree, create_stake_meta,
1616
ledger_utils::get_bank_from_snapshot_at_slot,
1717
load_bank_from_snapshot, merkle_tree_collection_file_name, meta_merkle_tree_path,
18-
process_epoch, read_merkle_tree_collection, read_stake_meta_collection,
18+
process_epoch, read_merkle_tree_collection, read_stake_meta_collection, reclaim,
1919
stake_meta_file_name,
2020
submit::{submit_recent_epochs_to_ncn, submit_to_ncn},
2121
tip_router::get_ncn_config,
@@ -49,7 +49,8 @@ async fn main() -> Result<()> {
4949
// Ensure backup directory and
5050
cli.force_different_backup_snapshot_dir();
5151

52-
let keypair = read_keypair_file(&cli.keypair_path).expect("Failed to read keypair file");
52+
let keypair =
53+
Arc::new(read_keypair_file(&cli.keypair_path).expect("Failed to read keypair file"));
5354
let rpc_client = Arc::new(RpcClient::new(cli.rpc_url.clone()));
5455

5556
datapoint_info!(
@@ -104,6 +105,7 @@ async fn main() -> Result<()> {
104105
claim_tips,
105106
claim_tips_metrics,
106107
claim_tips_epoch_lookback,
108+
reclaim_expired_accounts,
107109
} => {
108110
assert!(
109111
num_monitored_epochs > 0,
@@ -173,9 +175,9 @@ async fn main() -> Result<()> {
173175
}
174176
});
175177

178+
let keypair_arc = Arc::clone(&keypair);
176179
// Check for new meta merkle trees and submit to NCN periodically
177180
tokio::spawn(async move {
178-
let keypair_arc = Arc::new(keypair);
179181
loop {
180182
if let Err(e) = submit_recent_epochs_to_ncn(
181183
&rpc_client_clone,
@@ -196,13 +198,13 @@ async fn main() -> Result<()> {
196198
}
197199
});
198200

199-
let cli_clone: Cli = cli.clone();
201+
let save_path = cli.clone().get_save_path();
202+
200203
// Track incremental snapshots and backup to `backup_snapshots_dir`
201204
tokio::spawn(async move {
202-
let save_path = cli_clone.get_save_path();
203205
loop {
204206
if let Err(e) = BackupSnapshotMonitor::new(
205-
&rpc_url,
207+
rpc_url.clone().as_str(),
206208
full_snapshots_path.clone(),
207209
backup_snapshots_dir.clone(),
208210
override_target_slot,
@@ -367,7 +369,26 @@ async fn main() -> Result<()> {
367369
});
368370
}
369371

370-
// Endless loop that transitions between stages of the operator process.
372+
if reclaim_expired_accounts {
373+
let rpc_url = cli.rpc_url.clone();
374+
tokio::spawn(async move {
375+
loop {
376+
info!("Checking for expired accounts to close...");
377+
if let Err(e) = reclaim::close_expired_accounts(
378+
&rpc_url,
379+
tip_distribution_program_id,
380+
priority_fee_distribution_program_id,
381+
Arc::clone(&keypair),
382+
num_monitored_epochs,
383+
)
384+
.await
385+
{
386+
error!("Error closing expired accounts: {}", e);
387+
}
388+
sleep(Duration::from_secs(1800)).await;
389+
}
390+
});
391+
} // Endless loop that transitions between stages of the operator process.
371392
process_epoch::loop_stages(
372393
rpc_client,
373394
cli,
@@ -406,7 +427,7 @@ async fn main() -> Result<()> {
406427
let operator_address = Pubkey::from_str(&cli.operator_address)?;
407428
submit_to_ncn(
408429
&rpc_client,
409-
&keypair,
430+
&keypair.clone(),
410431
&operator_address,
411432
&meta_merkle_tree_path,
412433
epoch,

tip-router-operator-cli/src/process_epoch.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ use std::{
55
time::{Duration, Instant},
66
};
77

8+
use crate::{
9+
backup_snapshots::SnapshotInfo, cli::SnapshotPaths, create_merkle_tree_collection,
10+
create_meta_merkle_tree, create_stake_meta, ledger_utils::get_bank_from_snapshot_at_slot,
11+
load_bank_from_snapshot, meta_merkle_tree_path, read_merkle_tree_collection,
12+
read_stake_meta_collection, submit::submit_to_ncn, tip_router::get_ncn_config, Cli,
13+
OperatorState, Version,
14+
};
815
use anyhow::Result;
916
use log::{error, info};
1017
use meta_merkle_tree::generated_merkle_tree::{GeneratedMerkleTreeCollection, StakeMetaCollection};
@@ -14,14 +21,6 @@ use solana_runtime::bank::Bank;
1421
use solana_sdk::{epoch_info::EpochInfo, pubkey::Pubkey, signature::read_keypair_file};
1522
use tokio::time;
1623

17-
use crate::{
18-
backup_snapshots::SnapshotInfo, cli::SnapshotPaths, create_merkle_tree_collection,
19-
create_meta_merkle_tree, create_stake_meta, ledger_utils::get_bank_from_snapshot_at_slot,
20-
load_bank_from_snapshot, meta_merkle_tree_path, read_merkle_tree_collection,
21-
read_stake_meta_collection, submit::submit_to_ncn, tip_router::get_ncn_config, Cli,
22-
OperatorState, Version,
23-
};
24-
2524
const MAX_WAIT_FOR_INCREMENTAL_SNAPSHOT_TICKS: u64 = 1200; // Experimentally determined
2625
const OPTIMAL_INCREMENTAL_SNAPSHOT_SLOT_RANGE: u64 = 800; // Experimentally determined
2726

0 commit comments

Comments
 (0)