Skip to content

Commit

Permalink
add rpc to check balance against transaction (#1375)
Browse files Browse the repository at this point in the history
  • Loading branch information
zimpha authored and Peilun Li committed May 7, 2020
1 parent d7437d4 commit 64c978b
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 6 deletions.
43 changes: 42 additions & 1 deletion client/src/rpc/impls/cfx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::rpc::{
traits::{cfx::Cfx, debug::LocalRpc, test::TestRpc},
types::{
sign_call, Account as RpcAccount, BlameInfo, Block as RpcBlock,
BlockHashOrEpochNumber, Bytes, CallRequest, ConsensusGraphStates,
BlockHashOrEpochNumber, Bytes, CallRequest,
CheckBalanceAgainstTransactionResponse, ConsensusGraphStates,
EpochNumber, EstimateGasAndCollateralResponse, Filter as RpcFilter,
Log as RpcLog, Receipt as RpcReceipt, SendTxRequest,
SponsorInfo as RpcSponsorInfo, Status as RpcStatus,
Expand Down Expand Up @@ -793,6 +794,43 @@ impl RpcImpl {
Ok(response)
}

fn check_balance_against_transaction(
&self, account_addr: RpcH160, contract_addr: RpcH160,
gas_limit: RpcU256, gas_price: RpcU256, storage_limit: RpcU256,
epoch: Option<EpochNumber>,
) -> JsonRpcResult<CheckBalanceAgainstTransactionResponse>
{
let consensus_graph = self
.consensus
.as_any()
.downcast_ref::<ConsensusGraph>()
.expect("downcast should succeed");
let epoch = epoch.unwrap_or(EpochNumber::LatestState);

match consensus_graph.check_balance_against_transaction(
account_addr.into(),
contract_addr.into(),
gas_limit.into(),
gas_price.into(),
storage_limit.into(),
epoch.into(),
) {
Ok((will_pay_tx_fee, will_pay_collateral, is_balance_enough)) => {
let response = CheckBalanceAgainstTransactionResponse {
will_pay_tx_fee,
will_pay_collateral,
is_balance_enough,
};
Ok(response)
}
Err(e) => {
let mut rpc_error = JsonRpcError::internal_error();
rpc_error.message = format!("{:?}", e).into();
bail!(rpc_error)
}
}
}

fn exec_transaction(
&self, request: CallRequest, epoch: Option<EpochNumber>,
) -> RpcResult<ExecutionOutcome> {
Expand Down Expand Up @@ -924,6 +962,9 @@ impl Cfx for CfxHandler {
fn estimate_gas_and_collateral(
&self, request: CallRequest, epoch_number: Option<EpochNumber>)
-> JsonRpcResult<EstimateGasAndCollateralResponse>;
fn check_balance_against_transaction(
&self, account_addr: RpcH160, contract_addr: RpcH160, gas_limit: RpcU256, gas_price: RpcU256, storage_limit: RpcU256, epoch: Option<EpochNumber>,
) -> JsonRpcResult<CheckBalanceAgainstTransactionResponse>;
fn get_logs(&self, filter: RpcFilter) -> BoxFuture<Vec<RpcLog>>;
fn send_raw_transaction(&self, raw: Bytes) -> JsonRpcResult<RpcH256>;
fn storage_at(&self, addr: RpcH160, pos: RpcH256, epoch_number: Option<EpochNumber>)
Expand Down
4 changes: 3 additions & 1 deletion client/src/rpc/impls/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::rpc::{
traits::{cfx::Cfx, debug::LocalRpc, test::TestRpc},
types::{
Account as RpcAccount, BlameInfo, Block as RpcBlock,
BlockHashOrEpochNumber, Bytes, CallRequest, ConsensusGraphStates,
BlockHashOrEpochNumber, Bytes, CallRequest,
CheckBalanceAgainstTransactionResponse, ConsensusGraphStates,
EpochNumber, EstimateGasAndCollateralResponse, Filter as RpcFilter,
Log as RpcLog, Receipt as RpcReceipt, SendTxRequest,
SponsorInfo as RpcSponsorInfo, Status as RpcStatus,
Expand Down Expand Up @@ -492,6 +493,7 @@ impl Cfx for CfxHandler {
not_supported! {
fn accumulate_interest_rate(&self, num: Option<EpochNumber>) -> RpcResult<RpcU256>;
fn interest_rate(&self, num: Option<EpochNumber>) -> RpcResult<RpcU256>;
fn check_balance_against_transaction(&self, account_addr: RpcH160, contract_addr: RpcH160, gas_limit: RpcU256, gas_price: RpcU256, storage_limit: RpcU256, epoch: Option<EpochNumber>) -> RpcResult<CheckBalanceAgainstTransactionResponse>;
}
}

Expand Down
11 changes: 10 additions & 1 deletion client/src/rpc/traits/cfx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// See http://www.gnu.org/licenses/

use super::super::types::{
Account as RpcAccount, Block, Bytes, CallRequest, EpochNumber,
Account as RpcAccount, Block, Bytes, CallRequest,
CheckBalanceAgainstTransactionResponse, EpochNumber,
EstimateGasAndCollateralResponse, Filter as RpcFilter, Log as RpcLog,
Receipt as RpcReceipt, SponsorInfo as RpcSponsorInfo,
StorageRoot as RpcStorageRoot, Transaction, H160 as RpcH160,
Expand Down Expand Up @@ -158,6 +159,14 @@ pub trait Cfx {
&self, request: CallRequest, epoch_number: Option<EpochNumber>,
) -> JsonRpcResult<EstimateGasAndCollateralResponse>;

/// Check if user balance is enough for the transaction.
#[rpc(name = "cfx_checkBalanceAgainstTransaction")]
fn check_balance_against_transaction(
&self, account_addr: RpcH160, contract_addr: RpcH160,
gas_limit: RpcU256, gas_price: RpcU256, storage_limit: RpcU256,
epoch: Option<EpochNumber>,
) -> JsonRpcResult<CheckBalanceAgainstTransactionResponse>;

#[rpc(name = "cfx_getBlocksByEpoch")]
fn blocks_by_epoch(
&self, epoch_number: EpochNumber,
Expand Down
5 changes: 4 additions & 1 deletion client/src/rpc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ pub use self::{
blame_info::BlameInfo,
block::{Block, BlockTransactions, Header},
bytes::Bytes,
call_request::{sign_call, CallRequest, EstimateGasAndCollateralResponse},
call_request::{
sign_call, CallRequest, CheckBalanceAgainstTransactionResponse,
EstimateGasAndCollateralResponse,
},
consensus_graph_states::ConsensusGraphStates,
epoch_number::{BlockHashOrEpochNumber, EpochNumber},
filter::Filter,
Expand Down
11 changes: 11 additions & 0 deletions client/src/rpc/types/call_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ pub struct EstimateGasAndCollateralResponse {
pub storage_collateralized: U256,
}

#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckBalanceAgainstTransactionResponse {
/// Whether the account should pay transaction fee by self.
pub will_pay_tx_fee: bool,
/// Whether the account should pay collateral by self.
pub will_pay_collateral: bool,
/// Whether the account balance is enough for this transaction.
pub is_balance_enough: bool,
}

pub fn sign_call(
epoch_height: u64, chain_id: u64, request: CallRequest,
) -> SignedTransaction {
Expand Down
58 changes: 56 additions & 2 deletions core/src/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use crate::{
bytes::Bytes,
consensus::consensus_inner::consensus_executor::ConsensusExecutionConfiguration,
executive::ExecutionOutcome,
parameters::{consensus::*, consensus_internal::*},
parameters::{
consensus::*, consensus_internal::*, staking::COLLATERAL_PER_BYTE,
},
pow::ProofOfWorkConfig,
rpc_errors::Result as RpcResult,
state::State,
Expand All @@ -34,7 +36,7 @@ use crate::{
vm_factory::VmFactory,
Notifications,
};
use cfx_types::{Bloom, H160, H256, U256};
use cfx_types::{Bloom, H160, H256, U256, U512};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf as DeriveMallocSizeOf;
use metrics::{register_meter_with_group, Meter, MeterTimer};
Expand Down Expand Up @@ -889,6 +891,58 @@ impl ConsensusGraph {
self.executor.call_virtual(tx, &epoch_id)
}

pub fn check_balance_against_transaction(
&self, account_addr: H160, contract_addr: H160, gas_limit: U256,
gas_price: U256, storage_limit: U256, epoch: EpochNumber,
) -> RpcResult<(bool, bool, bool)>
{
self.validate_stated_epoch(&epoch)?;
let state_db = self.get_state_db_by_epoch_number(epoch)?;
// FIXME: check if we should fill the correct `block_number`.
let state = State::new(
state_db,
Default::default(), /* vm */
0, /* block_number */
);
let gas_cost = gas_limit.full_mul(gas_price);
let mut gas_sponsored = false;
let mut storage_sponsored = false;
if state.check_commission_privilege(&contract_addr, &account_addr)? {
// No need to check for gas sponsor account existence.
gas_sponsored = gas_cost
<= U512::from(state.sponsor_gas_bound(&contract_addr)?);
storage_sponsored =
state.sponsor_for_collateral(&contract_addr)?.is_some();
}
let gas_sponsor_balance = if gas_sponsored {
U512::from(state.sponsor_balance_for_gas(&contract_addr)?)
} else {
0.into()
};
let will_pay_tx_fee = !gas_sponsored || gas_sponsor_balance < gas_cost;

let storage_limit_in_drip =
if storage_limit >= U256::from(std::u64::MAX) {
U256::from(std::u64::MAX) * *COLLATERAL_PER_BYTE
} else {
storage_limit * *COLLATERAL_PER_BYTE
};
let storage_sponsor_balance = if storage_sponsored {
state.sponsor_balance_for_collateral(&contract_addr)?
} else {
0.into()
};

let will_pay_collateral = !storage_sponsored
|| storage_limit_in_drip > storage_sponsor_balance;

let balance = state.balance(&account_addr)?;
let minimum_balance = if will_pay_tx_fee { gas_cost } else { 0.into() };
let is_balance_enough = U512::from(balance) >= minimum_balance;

Ok((will_pay_tx_fee, will_pay_collateral, is_balance_enough))
}

/// Get the number of processed blocks (i.e., the number of calls to
/// on_new_block()
pub fn get_processed_block_count(&self) -> usize {
Expand Down
60 changes: 60 additions & 0 deletions tests/commission_privilege_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ def run_test(self):
(addr1, priv_key1) = client.rand_account()
(addr2, priv_key2) = client.rand_account()
(addr3, priv_key3) = client.rand_account()
(addr4, priv_key4) = client.rand_account()
tx = client.new_tx(
sender=genesis_addr,
priv_key=genesis_key,
Expand Down Expand Up @@ -223,6 +224,50 @@ def run_test(self):
assert_equal(client.get_sponsor_gas_bound(contract_addr), upper_bound)
assert_equal(client.get_balance(genesis_addr), b0 - 10 ** 18 - charged_of_huge_gas(gas))

check_info = client.check_balance_against_transaction(addr1, contract_addr, gas, gas_price, storage_limit=0)
assert_equal(check_info['willPayTxFee'], True)
assert_equal(check_info['willPayCollateral'], True)
assert_equal(check_info['isBalanceEnough'], True)

check_info = client.check_balance_against_transaction(addr4, contract_addr, gas, gas_price, storage_limit=0)
assert_equal(check_info['willPayTxFee'], True)
assert_equal(check_info['willPayCollateral'], True)
assert_equal(check_info['isBalanceEnough'], False)

# set privilege for addr4
b0 = client.get_balance(genesis_addr)
c0 = client.get_collateral_for_storage(genesis_addr)
self.call_contract_function(
contract=test_contract,
name="add",
args=[Web3.toChecksumAddress(addr4)],
sender_key=genesis_key,
contract_addr=contract_addr,
wait=True,
check_status=True,
storage_limit=64)
assert_equal(client.get_balance(genesis_addr), b0 - charged_of_huge_gas(gas) - collateral_per_storage_key)
assert_equal(client.get_collateral_for_storage(genesis_addr), c0 + collateral_per_storage_key)

check_info = client.check_balance_against_transaction(addr4, contract_addr, gas, gas_price, storage_limit=0)
assert_equal(check_info['willPayTxFee'], False)
assert_equal(check_info['willPayCollateral'], True)
assert_equal(check_info['isBalanceEnough'], True)

# remove privilege for addr4
b0 = client.get_balance(genesis_addr)
c0 = client.get_collateral_for_storage(genesis_addr)
self.call_contract_function(
contract=test_contract,
name="remove",
args=[Web3.toChecksumAddress(addr4)],
sender_key=genesis_key,
contract_addr=contract_addr,
wait=True,
check_status=True)
assert_equal(client.get_collateral_for_storage(genesis_addr), c0 - collateral_per_storage_key)
assert_equal(client.get_balance(genesis_addr), b0 - charged_of_huge_gas(gas) + collateral_per_storage_key)

# set privilege for addr1
b0 = client.get_balance(genesis_addr)
c0 = client.get_collateral_for_storage(genesis_addr)
Expand Down Expand Up @@ -414,6 +459,11 @@ def run_test(self):
assert_equal(client.get_sponsor_for_collateral(contract_addr), addr3)
assert_equal(client.get_balance(addr3), b3 - charged_of_huge_gas(gas) - 10 ** 18 + 1)

check_info = client.check_balance_against_transaction(addr1, contract_addr, gas, gas_price, storage_limit=0)
assert_equal(check_info['willPayTxFee'], True)
assert_equal(check_info['willPayCollateral'], True)
assert_equal(check_info['isBalanceEnough'], True)

# addr1 create 2 keys without privilege, and storage limit is 1, should failed
b1 = client.get_balance(addr1)
assert_equal(client.get_collateral_for_storage(contract_addr), 0)
Expand Down Expand Up @@ -458,6 +508,16 @@ def run_test(self):
assert_equal(client.get_collateral_for_storage(addr1), collateral_per_storage_key)
assert_equal(client.get_balance(addr1), b1 - charged_of_huge_gas(gas) + collateral_per_storage_key)

check_info = client.check_balance_against_transaction(addr2, contract_addr, gas, gas_price, storage_limit=bytes_per_key)
assert_equal(check_info['willPayTxFee'], False)
assert_equal(check_info['willPayCollateral'], False)
assert_equal(check_info['isBalanceEnough'], True)

check_info = client.check_balance_against_transaction(addr2, contract_addr, gas, gas_price, storage_limit=10 ** 18)
assert_equal(check_info['willPayTxFee'], False)
assert_equal(check_info['willPayCollateral'], True)
assert_equal(check_info['isBalanceEnough'], True)

# addr2 create 2 keys with privilege, and storage limit is 1, should succeed
sbc = client.get_sponsor_balance_for_collateral(contract_addr)
sbg = client.get_sponsor_balance_for_gas(contract_addr)
Expand Down
3 changes: 3 additions & 0 deletions tests/conflux/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ def estimate_collateral(self, contract_addr:str, data_hex:str, sender:str=None,
response = self.node.cfx_estimateGasAndCollateral(tx)
return response['storageCollateralized']

def check_balance_against_transaction(self, account_addr: str, contract_addr: str, gas_limit: int, gas_price: int, storage_limit: int) -> dict:
return self.node.cfx_checkBalanceAgainstTransaction(account_addr, contract_addr, hex(gas_limit), hex(gas_price), hex(storage_limit))

def call(self, contract_addr:str, data_hex:str, nonce=None, epoch:str=None) -> str:
tx = self.new_tx_for_call(contract_addr, data_hex, nonce=nonce)
if epoch is None:
Expand Down

0 comments on commit 64c978b

Please sign in to comment.