Skip to content

Commit

Permalink
AUDIT: Operator Index (#54)
Browse files Browse the repository at this point in the history
Operator Snapshot cannot be initialized if the `ncn_operator_state`'s
index is >= `epoch_snapshot`'s operator count.

This would happen if the NCN added the operator after the Epoch Snapshot
has been taken and the Operator count has been set
  • Loading branch information
coachchucksol authored Jan 17, 2025
1 parent 5b102ab commit 84de81c
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 2 deletions.
4 changes: 4 additions & 0 deletions clients/js/jito_tip_router/errors/jitoTipRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ export const JITO_TIP_ROUTER_ERROR__ACCOUNT_ALREADY_INITIALIZED = 0x2243; // 877
export const JITO_TIP_ROUTER_ERROR__BAD_BALLOT = 0x2244; // 8772
/** VotingIsNotOver: Cannot route until voting is over */
export const JITO_TIP_ROUTER_ERROR__VOTING_IS_NOT_OVER = 0x2245; // 8773
/** OperatorIsNotInSnapshot: Operator is not in snapshot */
export const JITO_TIP_ROUTER_ERROR__OPERATOR_IS_NOT_IN_SNAPSHOT = 0x2246; // 8774

export type JitoTipRouterError =
| typeof JITO_TIP_ROUTER_ERROR__ACCOUNT_ALREADY_INITIALIZED
Expand Down Expand Up @@ -225,6 +227,7 @@ export type JitoTipRouterError =
| typeof JITO_TIP_ROUTER_ERROR__NO_VAULTS_IN_REGISTRY
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_ADMIN_INVALID
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_IS_NOT_IN_SNAPSHOT
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_REWARD_LIST_FULL
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_REWARD_NOT_FOUND
| typeof JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL
Expand Down Expand Up @@ -308,6 +311,7 @@ if (process.env.NODE_ENV !== 'production') {
[JITO_TIP_ROUTER_ERROR__NO_VAULTS_IN_REGISTRY]: `There are no vaults in the registry`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_ADMIN_INVALID]: `Operator admin needs to sign its vote`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED]: `Operator is already finalized - should not happen`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_IS_NOT_IN_SNAPSHOT]: `Operator is not in snapshot`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_REWARD_LIST_FULL]: `Operator reward list full`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_REWARD_NOT_FOUND]: `Operator Reward not found`,
[JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL]: `Operator votes full`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ pub enum JitoTipRouterError {
/// 8773 - Cannot route until voting is over
#[error("Cannot route until voting is over")]
VotingIsNotOver = 0x2245,
/// 8774 - Operator is not in snapshot
#[error("Operator is not in snapshot")]
OperatorIsNotInSnapshot = 0x2246,
}

impl solana_program::program_error::PrintProgramError for JitoTipRouterError {
Expand Down
2 changes: 2 additions & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ pub enum TipRouterError {
BadBallot,
#[error("Cannot route until voting is over")]
VotingIsNotOver,
#[error("Operator is not in snapshot")]
OperatorIsNotInSnapshot,
}

impl<T> DecodeError<T> for TipRouterError {
Expand Down
5 changes: 5 additions & 0 deletions idl/jito_tip_router.json
Original file line number Diff line number Diff line change
Expand Up @@ -3622,6 +3622,11 @@
"code": 8773,
"name": "VotingIsNotOver",
"msg": "Cannot route until voting is over"
},
{
"code": 8774,
"name": "OperatorIsNotInSnapshot",
"msg": "Operator is not in snapshot"
}
],
"metadata": {
Expand Down
42 changes: 40 additions & 2 deletions integration_tests/tests/tip_router/initialize_operator_snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#[cfg(test)]
mod tests {

use jito_tip_router_core::{constants::MAX_REALLOC_BYTES, epoch_snapshot::OperatorSnapshot};
use jito_tip_router_core::{
constants::MAX_REALLOC_BYTES, epoch_snapshot::OperatorSnapshot, error::TipRouterError,
};

use crate::fixtures::{test_builder::TestBuilder, TestResult};
use crate::fixtures::{
test_builder::TestBuilder, tip_router_client::assert_tip_router_error, TestResult,
};

#[tokio::test]
async fn test_initialize_operator_snapshot() -> TestResult<()> {
Expand Down Expand Up @@ -61,4 +65,38 @@ mod tests {

Ok(())
}

#[tokio::test]
async fn test_add_operator_after_epoch_snapshot() -> TestResult<()> {
let mut fixture = TestBuilder::new().await;
let mut tip_router_client = fixture.tip_router_client();

let mut test_ncn = fixture.create_initial_test_ncn(1, 1, None).await?;
fixture.add_epoch_state_for_test_ncn(&test_ncn).await?;

fixture.warp_slot_incremental(1000).await?;

fixture.add_admin_weights_for_test_ncn(&test_ncn).await?;
fixture.add_epoch_snapshot_to_test_ncn(&test_ncn).await?;

// Add New Operator
fixture
.add_operators_to_test_ncn(&mut test_ncn, 1, None)
.await?;

let clock = fixture.clock().await;
let epoch = clock.epoch;
let ncn = test_ncn.ncn_root.ncn_pubkey;
// Last added operator
let operator = test_ncn.operators[1].operator_pubkey;

// Initialize operator snapshot
let result = tip_router_client
.do_initialize_operator_snapshot(operator, ncn, epoch)
.await;

assert_tip_router_error(result, TipRouterError::OperatorIsNotInSnapshot);

Ok(())
}
}
20 changes: 20 additions & 0 deletions program/src/initialize_operator_snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use jito_bytemuck::AccountDeserialize;
use jito_jsm_core::{
create_account,
loader::{load_signer, load_system_account, load_system_program},
Expand All @@ -8,6 +9,7 @@ use jito_tip_router_core::{
constants::MAX_REALLOC_BYTES,
epoch_snapshot::{EpochSnapshot, OperatorSnapshot},
epoch_state::EpochState,
error::TipRouterError,
};
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
Expand Down Expand Up @@ -53,6 +55,24 @@ pub fn process_initialize_operator_snapshot(
return Err(ProgramError::InvalidAccountData);
}

// Cannot create Operator snapshot if the operator index is greater than the operator count
{
let epoch_snapshot_data = epoch_snapshot.data.borrow();
let epoch_snapshot = EpochSnapshot::try_from_slice_unchecked(&epoch_snapshot_data)?;

let ncn_operator_state_data = ncn_operator_state.data.borrow();
let ncn_operator_state =
NcnOperatorState::try_from_slice_unchecked(&ncn_operator_state_data)?;

let operator_count = epoch_snapshot.operator_count();
let operator_index = ncn_operator_state.index();

if operator_index >= operator_count {
msg!("Operator index is out of bounds");
return Err(TipRouterError::OperatorIsNotInSnapshot.into());
}
}

msg!(
"Initializing Operator snapshot {} for NCN: {} at epoch: {}",
epoch_snapshot.key,
Expand Down

0 comments on commit 84de81c

Please sign in to comment.