Get Index Key Size RPC Support (#28383)

Co-authored-by: K-anon <IntokuSatori@users.noreply.github.com>
This commit is contained in:
K-anon 2022-11-16 20:39:11 -07:00 committed by GitHub
parent 83a7b2ca6c
commit e8c8235474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 489 additions and 16 deletions

View File

@ -137,6 +137,14 @@ pub struct RpcEpochConfig {
pub min_context_slot: Option<Slot>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub enum RpcAccountIndex {
ProgramId,
SplTokenMint,
SplTokenOwner,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccountInfoConfig {

View File

@ -114,6 +114,14 @@ type RpcCustomResult<T> = std::result::Result<T, RpcCustomError>;
pub const MAX_REQUEST_BODY_SIZE: usize = 50 * (1 << 10); // 50kB
pub const PERFORMANCE_SAMPLES_LIMIT: usize = 720;
fn rpc_account_index_from_account_index(account_index: &AccountIndex) -> RpcAccountIndex {
match account_index {
AccountIndex::ProgramId => RpcAccountIndex::ProgramId,
AccountIndex::SplTokenOwner => RpcAccountIndex::SplTokenOwner,
AccountIndex::SplTokenMint => RpcAccountIndex::SplTokenMint,
}
}
fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
RpcResponse {
context: RpcResponseContext::new(bank.slot()),
@ -496,6 +504,51 @@ impl JsonRpcRequestProcessor {
})
}
pub fn get_secondary_index_key_size(
&self,
index_key: &Pubkey,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>> {
// Acquire the bank
let bank = self.get_bank_with_config(config.unwrap_or_default())?;
// Exit if secondary indexes are not enabled
if self.config.account_indexes.is_empty() {
debug!("get_secondary_index_key_size: secondary index not enabled.");
return Ok(new_response(&bank, HashMap::new()));
};
// Make sure the requested key is not explicitly excluded
if !self.config.account_indexes.include_key(index_key) {
return Err(RpcCustomError::KeyExcludedFromSecondaryIndex {
index_key: index_key.to_string(),
}
.into());
}
// Grab a ref to the AccountsIndex for this Bank
let accounts_index = &bank.accounts().accounts_db.accounts_index;
// Find the size of the key in every index where it exists
let found_sizes = self
.config
.account_indexes
.indexes
.iter()
.filter_map(|index| {
accounts_index
.get_index_key_size(index, index_key)
.map(|size| (rpc_account_index_from_account_index(index), size))
})
.collect::<HashMap<_, _>>();
// Note: Will return an empty HashMap if no keys are found.
if found_sizes.is_empty() {
debug!("get_secondary_index_key_size: key not found in the secondary index.");
}
Ok(new_response(&bank, found_sizes))
}
pub async fn get_inflation_reward(
&self,
addresses: Vec<Pubkey>,
@ -2970,6 +3023,14 @@ pub mod rpc_accounts {
config: Option<RpcProgramAccountsConfig>,
) -> Result<OptionalContext<Vec<RpcKeyedAccount>>>;
#[rpc(meta, name = "getSecondaryIndexKeySize")]
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>>;
#[rpc(meta, name = "getBlockCommitment")]
fn get_block_commitment(
&self,
@ -3121,6 +3182,20 @@ pub mod rpc_accounts {
meta.get_program_accounts(&program_id, config, filters, with_context)
}
fn get_secondary_index_key_size(
&self,
meta: Self::Metadata,
pubkey_str: String,
config: Option<RpcContextConfig>,
) -> Result<RpcResponse<HashMap<RpcAccountIndex, usize>>> {
debug!(
"get_secondary_index_key_size rpc request received: {:?}",
pubkey_str
);
let index_key = verify_pubkey(&pubkey_str)?;
meta.get_secondary_index_key_size(&index_key, config)
}
fn get_block_commitment(
&self,
meta: Self::Metadata,
@ -4589,6 +4664,7 @@ pub mod tests {
jsonrpc_core::{futures, ErrorCode, MetaIoHandler, Output, Response, Value},
jsonrpc_core_client::transports::local,
serde::de::DeserializeOwned,
solana_account_decoder::parse_token::spl_token_pubkey,
solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta},
solana_entry::entry::next_versioned_entry,
solana_gossip::{contact_info::ContactInfo, socketaddr},
@ -4606,8 +4682,9 @@ pub mod tests {
filter::{Memcmp, MemcmpEncodedBytes},
},
solana_runtime::{
accounts_background_service::AbsRequestSender, commitment::BlockCommitment,
inline_spl_token, non_circulating_supply::non_circulating_accounts,
accounts_background_service::AbsRequestSender, bank::BankTestConfig,
commitment::BlockCommitment, inline_spl_token,
non_circulating_supply::non_circulating_accounts,
},
solana_sdk::{
account::{Account, WritableAccount},
@ -4704,7 +4781,18 @@ pub mod tests {
impl RpcHandler {
fn start() -> Self {
let (bank_forks, mint_keypair, leader_vote_keypair) = new_bank_forks();
Self::start_with_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
..JsonRpcConfig::default()
})
}
fn start_with_config(config: JsonRpcConfig) -> Self {
let (bank_forks, mint_keypair, leader_vote_keypair) =
new_bank_forks_with_config(BankTestConfig {
secondary_indexes: config.account_indexes.clone(),
});
let ledger_path = get_tmp_ledger_path!();
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
let bank = bank_forks.read().unwrap().working_bank();
@ -4731,10 +4819,7 @@ pub mod tests {
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(0));
let meta = JsonRpcRequestProcessor::new(
JsonRpcConfig {
enable_rpc_transaction_history: true,
..JsonRpcConfig::default()
},
config,
None,
bank_forks.clone(),
block_commitment_cache.clone(),
@ -6535,6 +6620,12 @@ pub mod tests {
}
fn new_bank_forks() -> (Arc<RwLock<BankForks>>, Keypair, Arc<Keypair>) {
new_bank_forks_with_config(BankTestConfig::default())
}
fn new_bank_forks_with_config(
config: BankTestConfig,
) -> (Arc<RwLock<BankForks>>, Keypair, Arc<Keypair>) {
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
@ -6548,7 +6639,7 @@ pub mod tests {
EpochSchedule::custom(TEST_SLOTS_PER_EPOCH, TEST_SLOTS_PER_EPOCH, false);
genesis_config.fee_rate_governor = FeeRateGovernor::new(TEST_SIGNATURE_FEE, 0);
let bank = Bank::new_for_tests(&genesis_config);
let bank = Bank::new_for_tests_with_config(&genesis_config, config);
(
Arc::new(RwLock::new(BankForks::new(bank))),
mint_keypair,
@ -7343,6 +7434,323 @@ pub mod tests {
));
}
#[test]
fn test_secondary_index_key_sizes() {
for secondary_index_enabled in [true, false] {
let account_indexes = if secondary_index_enabled {
AccountSecondaryIndexes {
keys: None,
indexes: HashSet::from([
AccountIndex::ProgramId,
AccountIndex::SplTokenMint,
AccountIndex::SplTokenOwner,
]),
}
} else {
AccountSecondaryIndexes::default()
};
// RPC & Bank Setup
let rpc = RpcHandler::start_with_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
account_indexes,
..JsonRpcConfig::default()
});
let bank = rpc.working_bank();
let RpcHandler { io, meta, .. } = rpc;
// Pubkeys
let token_account1_pubkey = Pubkey::new_unique();
let token_account2_pubkey = Pubkey::new_unique();
let token_account3_pubkey = Pubkey::new_unique();
let mint1_pubkey = Pubkey::new_unique();
let mint2_pubkey = Pubkey::new_unique();
let wallet1_pubkey = Pubkey::new_unique();
let wallet2_pubkey = Pubkey::new_unique();
let non_existent_pubkey = Pubkey::new_unique();
let delegate = spl_token_pubkey(&Pubkey::new_unique());
let mut num_default_spl_token_program_accounts = 0;
let mut num_default_system_program_accounts = 0;
if !secondary_index_enabled {
// Test first with no accounts added & no secondary indexes enabled:
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
token_account1_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
} else {
// Count SPL Token Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_spl_token_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
// Count System Program Default Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
num_default_system_program_accounts =
*sizes.get(&RpcAccountIndex::ProgramId).unwrap();
}
// Add 2 basic wallet accounts
let wallet1_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet1_pubkey, &wallet1_account);
let wallet2_account = AccountSharedData::from(Account {
lamports: 11111111,
owner: system_program::id(),
..Account::default()
});
bank.store_account(&wallet2_pubkey, &wallet2_account);
// Add a token account
let mut account1_data = vec![0; TokenAccount::get_packed_len()];
let token_account1 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet1_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
TokenAccount::pack(token_account1, &mut account1_data).unwrap();
let token_account1 = AccountSharedData::from(Account {
lamports: 111,
data: account1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account1_pubkey, &token_account1);
// Add the mint
let mut mint1_data = vec![0; Mint::get_packed_len()];
let mint1_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
supply: 500,
decimals: 2,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet1_pubkey)),
};
Mint::pack(mint1_state, &mut mint1_data).unwrap();
let mint_account1 = AccountSharedData::from(Account {
lamports: 222,
data: mint1_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint1_pubkey, &mint_account1);
// Add another token account with the different owner, but same delegate, and mint
let mut account2_data = vec![0; TokenAccount::get_packed_len()];
let token_account2 = TokenAccount {
mint: spl_token_pubkey(&mint1_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 420,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account2, &mut account2_data).unwrap();
let token_account2 = AccountSharedData::from(Account {
lamports: 333,
data: account2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account2_pubkey, &token_account2);
// Add another token account with the same owner and delegate but different mint
let mut account3_data = vec![0; TokenAccount::get_packed_len()];
let token_account3 = TokenAccount {
mint: spl_token_pubkey(&mint2_pubkey),
owner: spl_token_pubkey(&wallet2_pubkey),
delegate: COption::Some(delegate),
amount: 42,
state: TokenAccountState::Initialized,
is_native: COption::None,
delegated_amount: 30,
close_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
TokenAccount::pack(token_account3, &mut account3_data).unwrap();
let token_account3 = AccountSharedData::from(Account {
lamports: 444,
data: account3_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&token_account3_pubkey, &token_account3);
// Add the new mint
let mut mint2_data = vec![0; Mint::get_packed_len()];
let mint2_state = Mint {
mint_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
supply: 200,
decimals: 3,
is_initialized: true,
freeze_authority: COption::Some(spl_token_pubkey(&wallet2_pubkey)),
};
Mint::pack(mint2_state, &mut mint2_data).unwrap();
let mint_account2 = AccountSharedData::from(Account {
lamports: 555,
data: mint2_data.to_vec(),
owner: inline_spl_token::id(),
..Account::default()
});
bank.store_account(&mint2_pubkey, &mint_account2);
// Accounts should now look like the following:
//
// -----system_program------
// / \
// /-(owns) \-(owns)
// / \
// wallet1 ---wallet2---
// / / \
// /-(SPL::owns) /-(SPL::owns) \-(SPL::owns)
// / / \
// token_account1 token_account2 token_account3
// \ / /
// \-(SPL::mint) /-(SPL::mint) /-(SPL::mint)
// \ / /
// --mint_account1-- mint_account2
if secondary_index_enabled {
// ----------- Test for a non-existant key -----------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
non_existent_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
// --------------- Test Queries ---------------
// 1) Wallet1 - Owns 1 SPL Token
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
wallet1_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 1);
// 2) Wallet2 - Owns 2 SPL Tokens
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
wallet2_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenOwner).unwrap(), 2);
// 3) Mint1 - Is in 2 SPL Accounts
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
mint1_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 2);
// 4) Mint2 - Is in 1 SPL Account
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
mint2_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(*sizes.get(&RpcAccountIndex::SplTokenMint).unwrap(), 1);
// 5) SPL Token Program Owns 6 Accounts - 1 Default, 5 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
inline_spl_token::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_spl_token_program_accounts + 5)
);
// 5) System Program Owns 4 Accounts + 2 Default, 2 created above.
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
system_program::id(),
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert_eq!(sizes.len(), 1);
assert_eq!(
*sizes.get(&RpcAccountIndex::ProgramId).unwrap(),
(num_default_system_program_accounts + 2)
);
} else {
// ------------ Secondary Indexes Disabled ------------
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSecondaryIndexKeySize","params":["{}"]}}"#,
token_account2_pubkey,
);
let res = io.handle_request_sync(&req, meta.clone());
let result: Value = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
let sizes: HashMap<RpcAccountIndex, usize> =
serde_json::from_value(result["result"]["value"].clone()).unwrap();
assert!(sizes.is_empty());
}
}
}
#[test]
fn test_token_rpcs() {
for program_id in solana_account_decoder::parse_token::spl_token_ids() {

View File

@ -1472,6 +1472,22 @@ impl<T: IndexValue> AccountsIndex<T> {
}
}
pub fn get_index_key_size(&self, index: &AccountIndex, index_key: &Pubkey) -> Option<usize> {
match index {
AccountIndex::ProgramId => self.program_id_index.index.get(index_key).map(|x| x.len()),
AccountIndex::SplTokenOwner => self
.spl_token_owner_index
.index
.get(index_key)
.map(|x| x.len()),
AccountIndex::SplTokenMint => self
.spl_token_mint_index
.index
.get(index_key)
.map(|x| x.len()),
}
}
/// log any secondary index counts, if non-zero
pub(crate) fn log_secondary_indexes(&self) {
if !self.program_id_index.index.is_empty() {

View File

@ -1147,6 +1147,11 @@ pub struct NewBankOptions {
pub vote_only_bank: bool,
}
#[derive(Debug, Default)]
pub struct BankTestConfig {
pub secondary_indexes: AccountSecondaryIndexes,
}
#[derive(Debug)]
struct PrevEpochInflationRewards {
validator_rewards: u64,
@ -1209,9 +1214,16 @@ impl Bank {
}
pub fn new_for_tests(genesis_config: &GenesisConfig) -> Self {
Self::new_for_tests_with_config(genesis_config, BankTestConfig::default())
}
pub fn new_for_tests_with_config(
genesis_config: &GenesisConfig,
test_config: BankTestConfig,
) -> Self {
Self::new_with_config_for_tests(
genesis_config,
AccountSecondaryIndexes::default(),
test_config.secondary_indexes,
false,
AccountShrinkThreshold::default(),
)

View File

@ -823,6 +823,7 @@ impl TestValidator {
staked_nodes_overrides: config.staked_nodes_overrides.clone(),
accounts_db_config,
runtime_config,
account_indexes: config.rpc_config.account_indexes.clone(),
..ValidatorConfig::default_for_test()
};
if let Some(ref tower_storage) = config.tower_storage {

View File

@ -16,6 +16,7 @@ use {
rpc_pubsub_service::PubSubConfig,
},
solana_rpc_client::rpc_client::RpcClient,
solana_runtime::accounts_index::{AccountIndex, AccountSecondaryIndexes},
solana_sdk::{
account::AccountSharedData,
clock::Slot,
@ -140,6 +141,15 @@ fn main() {
.conflicts_with("quiet")
.help("Log mode: stream the validator log"),
)
.arg(
Arg::with_name("account_indexes")
.long("account-index")
.takes_value(true)
.multiple(true)
.possible_values(&["program-id", "spl-token-owner", "spl-token-mint"])
.value_name("INDEX")
.help("Enable an accounts index, indexed by the selected account field"),
)
.arg(
Arg::with_name("faucet_port")
.long("faucet-port")
@ -441,6 +451,22 @@ fn main() {
let ledger_path = value_t_or_exit!(matches, "ledger_path", PathBuf);
let reset_ledger = matches.is_present("reset");
let indexes: HashSet<AccountIndex> = matches
.values_of("account_indexes")
.unwrap_or_default()
.map(|value| match value {
"program-id" => AccountIndex::ProgramId,
"spl-token-mint" => AccountIndex::SplTokenMint,
"spl-token-owner" => AccountIndex::SplTokenOwner,
_ => unreachable!(),
})
.collect();
let account_indexes = AccountSecondaryIndexes {
keys: None,
indexes,
};
if !ledger_path.exists() {
fs::create_dir(&ledger_path).unwrap_or_else(|err| {
println!(
@ -752,13 +778,6 @@ fn main() {
faucet_pubkey,
AccountSharedData::new(faucet_lamports, 0, &system_program::id()),
)
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
enable_extended_tx_metadata_storage: true,
rpc_bigtable_config,
faucet_addr,
..JsonRpcConfig::default_for_test()
})
.pubsub_config(PubSubConfig {
enable_vote_subscription,
..PubSubConfig::default()
@ -778,6 +797,15 @@ fn main() {
})
.deactivate_features(&features_to_deactivate);
genesis.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
enable_extended_tx_metadata_storage: true,
rpc_bigtable_config,
faucet_addr,
account_indexes,
..JsonRpcConfig::default_for_test()
});
if !accounts_to_clone.is_empty() {
if let Err(e) = genesis.clone_accounts(
accounts_to_clone,