diff --git a/tip-router-operator-cli/src/cli.rs b/tip-router-operator-cli/src/cli.rs index 03b171b..09169c5 100644 --- a/tip-router-operator-cli/src/cli.rs +++ b/tip-router-operator-cli/src/cli.rs @@ -63,6 +63,9 @@ pub enum Commands { #[arg(long, env)] override_target_slot: Option, + + #[arg(long, env, default_value = "false")] + set_merkle_roots: bool, }, SnapshotSlot { #[arg(short, long, env)] diff --git a/tip-router-operator-cli/src/main.rs b/tip-router-operator-cli/src/main.rs index 5b453b7..972e688 100644 --- a/tip-router-operator-cli/src/main.rs +++ b/tip-router-operator-cli/src/main.rs @@ -29,17 +29,17 @@ async fn main() -> Result<()> { let rpc_client = EllipsisClient::from_rpc_with_timeout( RpcClient::new(cli.rpc_url.clone()), &read_keypair_file(&cli.keypair_path).expect("Failed to read keypair file"), - 60_000, + 1_800_000, // 30 minutes )?; set_host_id(cli.operator_address.to_string()); // Ensure tx submission works - let test_meta_merkle_root = [1; 32]; - let ix = spl_memo::build_memo(&test_meta_merkle_root.to_vec(), &[&keypair.pubkey()]); - info!("Submitting test tx {:?}", test_meta_merkle_root); - let tx = Transaction::new_with_payer(&[ix], Some(&keypair.pubkey())); - rpc_client.process_transaction(tx, &[&keypair]).await?; + // let test_meta_merkle_root = [1; 32]; + // let ix = spl_memo::build_memo(&test_meta_merkle_root.to_vec(), &[&keypair.pubkey()]); + // info!("Submitting test tx {:?}", test_meta_merkle_root); + // let tx = Transaction::new_with_payer(&[ix], Some(&keypair.pubkey())); + // rpc_client.process_transaction(tx, &[&keypair]).await?; info!( "CLI Arguments: @@ -69,6 +69,7 @@ async fn main() -> Result<()> { num_monitored_epochs, start_next_epoch, override_target_slot, + set_merkle_roots, } => { info!("Running Tip Router..."); info!("NCN Address: {}", ncn_address); @@ -110,6 +111,7 @@ async fn main() -> Result<()> { &tip_distribution_program_id, num_monitored_epochs, &cli_clone, + set_merkle_roots, ) .await { @@ -240,6 +242,7 @@ async fn main() -> Result<()> { &tip_router_program_id, &tip_distribution_program_id, cli.submit_as_memo, + true, ) .await?; } diff --git a/tip-router-operator-cli/src/submit.rs b/tip-router-operator-cli/src/submit.rs index 21938a2..5ffe0dd 100644 --- a/tip-router-operator-cli/src/submit.rs +++ b/tip-router-operator-cli/src/submit.rs @@ -1,13 +1,15 @@ +use std::time::Duration; use std::{path::PathBuf, str::FromStr}; use anchor_lang::AccountDeserialize; use ellipsis_client::EllipsisClient; use jito_bytemuck::AccountDeserialize as JitoAccountDeserialize; use jito_tip_distribution_sdk::{derive_config_account_address, TipDistributionAccount}; -use jito_tip_router_core::ballot_box::BallotBox; +use jito_tip_router_core::{ballot_box::BallotBox, config::Config}; use log::{debug, error, info}; use meta_merkle_tree::meta_merkle_tree::MetaMerkleTree; use solana_account_decoder::UiAccountEncoding; +use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient; use solana_client::{ rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, rpc_filter::{Memcmp, RpcFilterType}, @@ -28,6 +30,7 @@ pub async fn submit_recent_epochs_to_ncn( tip_distribution_program_id: &Pubkey, num_monitored_epochs: u64, cli_args: &Cli, + set_merkle_roots: bool, ) -> Result<(), anyhow::Error> { let epoch = client.get_epoch_info()?; let operator_address = Pubkey::from_str(&cli_args.operator_address)?; @@ -52,6 +55,7 @@ pub async fn submit_recent_epochs_to_ncn( tip_router_program_id, tip_distribution_program_id, cli_args.submit_as_memo, + set_merkle_roots, ) .await { @@ -73,9 +77,11 @@ pub async fn submit_to_ncn( tip_router_program_id: &Pubkey, tip_distribution_program_id: &Pubkey, submit_as_memo: bool, + set_merkle_roots: bool, ) -> Result<(), anyhow::Error> { let epoch_info = client.get_epoch_info()?; let meta_merkle_tree = MetaMerkleTree::new_from_file(meta_merkle_tree_path)?; + let config_pda = Config::find_program_address(tip_router_program_id, ncn_address).0; let config = get_ncn_config(client, tip_router_program_id, ncn_address).await?; // The meta merkle root files are tagged with the epoch they have created the snapshot for @@ -180,16 +186,22 @@ pub async fn submit_to_ncn( } } - if ballot_box.is_consensus_reached() { + if ballot_box.is_consensus_reached() && set_merkle_roots { // Fetch TipDistributionAccounts filtered by epoch and upload authority // Tip distribution accounts are derived from the epoch they are for let tip_distribution_accounts = get_tip_distribution_accounts_to_upload( client, merkle_root_epoch, + &config_pda, tip_distribution_program_id, ) .await?; + info!( + "Setting merkle roots for {} tip distribution accounts", + tip_distribution_accounts.len() + ); + // For each TipDistributionAccount returned, if it has no root uploaded, upload root with set_merkle_root match set_merkle_roots_batched( client, @@ -238,17 +250,17 @@ pub async fn submit_to_ncn( async fn get_tip_distribution_accounts_to_upload( client: &EllipsisClient, epoch: u64, - + tip_router_config_address: &Pubkey, tip_distribution_program_id: &Pubkey, ) -> Result, anyhow::Error> { - let config_address = derive_config_account_address(tip_distribution_program_id).0; + let rpc_client = AsyncRpcClient::new_with_timeout(client.url(), Duration::from_secs(1800)); // Filters assume merkle root is None let filters = vec![ RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8 // Discriminator + 32, // Pubkey - validator_vote_account - config_address.to_bytes().to_vec(), + tip_router_config_address.to_bytes().to_vec(), )), RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8 // Discriminator @@ -259,17 +271,19 @@ async fn get_tip_distribution_accounts_to_upload( )), ]; - let tip_distribution_accounts = client.get_program_accounts_with_config( - tip_distribution_program_id, - RpcProgramAccountsConfig { - filters: Some(filters), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64), - ..RpcAccountInfoConfig::default() + let tip_distribution_accounts = rpc_client + .get_program_accounts_with_config( + tip_distribution_program_id, + RpcProgramAccountsConfig { + filters: Some(filters), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + ..RpcAccountInfoConfig::default() + }, + ..RpcProgramAccountsConfig::default() }, - ..RpcProgramAccountsConfig::default() - }, - )?; + ) + .await?; let tip_distribution_accounts = tip_distribution_accounts .into_iter() @@ -280,7 +294,8 @@ async fn get_tip_distribution_accounts_to_upload( Ok(tip_distribution_account) => { // Double check that GPA filter worked if tip_distribution_account.epoch_created_at == epoch - && tip_distribution_account.merkle_root_upload_authority == config_address + && tip_distribution_account.merkle_root_upload_authority + == *tip_router_config_address { Some((pubkey, tip_distribution_account)) } else { diff --git a/tip-router-operator-cli/src/tip_router.rs b/tip-router-operator-cli/src/tip_router.rs index 624abff..9d32e7d 100644 --- a/tip-router-operator-cli/src/tip_router.rs +++ b/tip-router-operator-cli/src/tip_router.rs @@ -11,7 +11,7 @@ use jito_tip_router_core::{ epoch_snapshot::{EpochSnapshot, OperatorSnapshot}, epoch_state::EpochState, }; -use log::info; +use log::{error, info}; use meta_merkle_tree::meta_merkle_tree::MetaMerkleTree; use solana_sdk::{ pubkey::Pubkey, @@ -114,7 +114,7 @@ pub async fn set_merkle_roots_batched( let proof = if let Some(proof) = meta_merkle_node.proof { proof } else { - // TODO emit big warning NO PROOF + error!("No proof found for tip distribution account {:?}", key); return None; };