From 67d07254c284d5a4525a10516ff85685dd27bb5e Mon Sep 17 00:00:00 2001 From: Parth Date: Thu, 26 Sep 2019 23:27:13 +0530 Subject: [PATCH] Add rent estimation rpc (#6109) * server side new rpc endpoint * client side rpc * take data_len as usize Co-Authored-By: Tyera Eulberg * add test and documentation --- book/src/api-reference/jsonrpc-api.md | 23 ++++++++++ client/src/rpc_client.rs | 29 +++++++++++++ client/src/rpc_request.rs | 2 + core/src/rpc.rs | 61 ++++++++++++++++++++++++++- runtime/src/bank.rs | 6 +++ 5 files changed, 120 insertions(+), 1 deletion(-) diff --git a/book/src/api-reference/jsonrpc-api.md b/book/src/api-reference/jsonrpc-api.md index f20a1a0cd..932c6c6d4 100644 --- a/book/src/api-reference/jsonrpc-api.md +++ b/book/src/api-reference/jsonrpc-api.md @@ -30,6 +30,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- * [getStorageTurn](jsonrpc-api.md#getstorageturn) * [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate) * [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation) +* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption) * [getTransactionCount](jsonrpc-api.md#gettransactioncount) * [getTotalSupply](jsonrpc-api.md#gettotalsupply) * [getVersion](jsonrpc-api.md#getversion) @@ -455,6 +456,28 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, " {"jsonrpc":"2.0","result":8,"id":1} ``` +### getMinimumBalanceForRentExemption + +Returns minimum balance required to make account rent exempt. + +#### Parameters: + +* `integer` - account data length, as unsigned integer + +#### Results: + +* `integer` - minimum lamports required in account, as unsigned 64-bit integer + +#### Example: + +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getMinimumBalanceForRentExemption", "params":[50]}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":500,"id":1} +``` + ### getTransactionCount Returns the current Transaction count from the ledger diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index d1e2f8c17..d6e161b2e 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -306,6 +306,35 @@ impl RpcClient { self.get_account(pubkey).map(|account| account.data) } + pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> io::Result { + let params = json!([data_len]); + let minimum_balance_json = self + .client + .send( + &RpcRequest::GetMinimumBalanceForRentExemption, + Some(params), + 0, + ) + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!( + "GetMinimumBalanceForRentExemption request failure: {:?}", + err + ), + ) + })?; + + let minimum_balance: u64 = + serde_json::from_value(minimum_balance_json).expect("deserialize minimum_balance"); + trace!( + "Response minimum balance {:?} {:?}", + data_len, + minimum_balance + ); + Ok(minimum_balance) + } + /// Request the balance of the user holding `pubkey`. This method blocks /// until the server sends a response. If the response packet is dropped /// by the network, this method will hang indefinitely. diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index c0b0cdeb5..f5592596b 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -28,6 +28,7 @@ pub enum RpcRequest { RequestAirdrop, SendTransaction, SignVote, + GetMinimumBalanceForRentExemption, } impl RpcRequest { @@ -61,6 +62,7 @@ impl RpcRequest { RpcRequest::RequestAirdrop => "requestAirdrop", RpcRequest::SendTransaction => "sendTransaction", RpcRequest::SignVote => "signVote", + RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption", }; let mut request = json!({ "jsonrpc": jsonrpc, diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 942c757a0..8d8523867 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -73,6 +73,10 @@ impl JsonRpcRequestProcessor { .ok_or_else(Error::invalid_request) } + pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result { + Ok(self.bank().get_minimum_balance_for_rent_exemption(data_len)) + } + pub fn get_program_accounts(&self, program_id: &Pubkey) -> Result> { Ok(self .bank() @@ -299,6 +303,9 @@ pub trait RpcSol { #[rpc(meta, name = "getProgramAccounts")] fn get_program_accounts(&self, _: Self::Metadata, _: String) -> Result>; + #[rpc(meta, name = "getMinimumBalanceForRentExemption")] + fn get_minimum_balance_for_rent_exemption(&self, _: Self::Metadata, _: usize) -> Result; + #[rpc(meta, name = "getInflation")] fn get_inflation(&self, _: Self::Metadata) -> Result; @@ -407,6 +414,21 @@ impl RpcSol for RpcSolImpl { .get_account_info(&pubkey) } + fn get_minimum_balance_for_rent_exemption( + &self, + meta: Self::Metadata, + data_len: usize, + ) -> Result { + debug!( + "get_minimum_balance_for_rent_exemption rpc request received: {:?}", + data_len + ); + meta.request_processor + .read() + .unwrap() + .get_minimum_balance_for_rent_exemption(data_len) + } + fn get_program_accounts( &self, meta: Self::Metadata, @@ -877,6 +899,39 @@ pub mod tests { assert!(supply >= TEST_MINT_LAMPORTS); } + #[test] + fn test_rpc_get_minimum_balance_for_rent_exemption() { + let bob_pubkey = Pubkey::new_rand(); + let data_len = 50; + let (io, meta, bank, _blockhash, _alice, _leader_pubkey) = + start_rpc_handler_with_tx(&bob_pubkey); + + let req = format!( + r#"{{"jsonrpc":"2.0","id":1,"method":"getMinimumBalanceForRentExemption","params":[{}]}}"#, + data_len + ); + let rep = io.handle_request_sync(&req, meta); + let res: Response = serde_json::from_str(&rep.expect("actual response")) + .expect("actual response deserialization"); + let minimum_balance: u64 = if let Response::Single(res) = res { + if let Output::Success(res) = res { + if let Value::Number(num) = res.result { + num.as_u64().unwrap() + } else { + panic!("Expected number"); + } + } else { + panic!("Expected success"); + } + } else { + panic!("Expected single response"); + }; + assert_eq!( + minimum_balance, + bank.get_minimum_balance_for_rent_exemption(data_len) + ); + } + #[test] fn test_rpc_get_inflation() { let bob_pubkey = Pubkey::new_rand(); @@ -1171,10 +1226,14 @@ pub mod tests { fn new_bank_forks() -> (Arc>, Keypair) { let GenesisBlockInfo { - genesis_block, + mut genesis_block, mint_keypair, .. } = create_genesis_block(TEST_MINT_LAMPORTS); + + genesis_block.rent_calculator.lamports_per_byte_year = 50; + genesis_block.rent_calculator.exemption_threshold = 2.0; + let bank = Bank::new(&genesis_block); ( Arc::new(RwLock::new(BankForks::new(bank.slot(), bank))), diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 028744056..6a751d754 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -679,6 +679,12 @@ impl Bank { self.blockhash_queue.read().unwrap().last_hash() } + pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> u64 { + self.rent_collector + .rent_calculator + .minimum_balance(data_len) + } + pub fn last_blockhash_with_fee_calculator(&self) -> (Hash, FeeCalculator) { let blockhash_queue = self.blockhash_queue.read().unwrap(); let last_hash = blockhash_queue.last_hash();