@@ -21,6 +21,7 @@ use crate::{
2121} ;
2222use ethereum:: { BlockV0 as EthereumBlock , TransactionV0 as EthereumTransaction } ;
2323use ethereum_types:: { H160 , H256 , H512 , H64 , U256 , U64 } ;
24+ use evm:: ExitReason ;
2425use fc_rpc_core:: {
2526 types:: {
2627 Block , BlockNumber , BlockTransactions , Bytes , CallRequest , Filter , FilterChanges ,
@@ -31,11 +32,11 @@ use fc_rpc_core::{
3132} ;
3233use fp_rpc:: { ConvertTransaction , EthereumRuntimeRPCApi , TransactionStatus } ;
3334use futures:: { future:: TryFutureExt , StreamExt } ;
34- use lru:: LruCache ;
3535use jsonrpc_core:: {
3636 futures:: future:: { self , Future } ,
37- BoxFuture , ErrorCode , Result ,
37+ BoxFuture , Result ,
3838} ;
39+ use lru:: LruCache ;
3940use sc_client_api:: {
4041 backend:: { AuxStore , Backend , StateBackend , StorageProvider } ,
4142 client:: BlockchainEvents ,
@@ -951,29 +952,69 @@ where
951952 }
952953
953954 fn estimate_gas ( & self , request : CallRequest , _: Option < BlockNumber > ) -> Result < U256 > {
954- let gas_limit = {
955- // query current block's gas limit
956- let substrate_hash = self . client . info ( ) . best_hash ;
957- let id = BlockId :: Hash ( substrate_hash) ;
958- let schema =
959- frontier_backend_client:: onchain_storage_schema :: < B , C , BE > ( & self . client , id) ;
960- let handler = self
961- . overrides
962- . schemas
963- . get ( & schema)
964- . unwrap_or ( & self . overrides . fallback ) ;
955+ // Get best hash
956+ let best_hash = self . client . info ( ) . best_hash ;
965957
966- let block = self . block_data_cache . current_block ( handler, substrate_hash) ;
967- if let Some ( block) = block {
968- block. header . gas_limit
969- } else {
970- return Err ( internal_err ( "block unavailable, cannot query gas limit" ) ) ;
958+ // Get gas price
959+ let gas_price = request. gas_price . unwrap_or_default ( ) ;
960+
961+ // Determine the highest possible gas limits
962+ let mut highest = match request. gas {
963+ Some ( gas) => gas,
964+ None => {
965+ // query current block's gas limit
966+ let substrate_hash = self . client . info ( ) . best_hash ;
967+ let id = BlockId :: Hash ( substrate_hash) ;
968+ let schema =
969+ frontier_backend_client:: onchain_storage_schema :: < B , C , BE > ( & self . client , id) ;
970+ let handler = self
971+ . overrides
972+ . schemas
973+ . get ( & schema)
974+ . unwrap_or ( & self . overrides . fallback ) ;
975+
976+ let block = self . block_data_cache . current_block ( handler, substrate_hash) ;
977+ if let Some ( block) = block {
978+ block. header . gas_limit
979+ } else {
980+ return Err ( internal_err ( "block unavailable, cannot query gas limit" ) ) ;
981+ }
971982 }
972983 } ;
973984
974- let calculate_gas_used = |request, gas_limit| -> Result < U256 > {
975- let hash = self . client . info ( ) . best_hash ;
985+ // Recap the highest gas allowance with account's balance.
986+ if let Some ( from) = request. from {
987+ if gas_price > U256 :: zero ( ) {
988+ let balance = self
989+ . client
990+ . runtime_api ( )
991+ . account_basic ( & BlockId :: Hash ( best_hash) , from)
992+ . map_err ( |err| internal_err ( format ! ( "runtime error: {:?}" , err) ) ) ?
993+ . balance ;
994+ let mut available = balance;
995+ if let Some ( value) = request. value {
996+ if value > available {
997+ return Err ( internal_err ( "insufficient funds for transfer" ) ) ;
998+ }
999+ available -= value;
1000+ }
1001+ let allowance = available / gas_price;
1002+ if highest > allowance {
1003+ log:: warn!(
1004+ "Gas estimation capped by limited funds original {} balance {} sent {} feecap {} fundable {}" ,
1005+ highest,
1006+ balance,
1007+ request. value. unwrap_or_default( ) ,
1008+ gas_price,
1009+ allowance
1010+ ) ;
1011+ highest = allowance;
1012+ }
1013+ }
1014+ }
9761015
1016+ // Create a helper to check if a gas allowance results in an executable transaction
1017+ let executable = move |request : CallRequest , gas_limit| -> Result < Option < U256 > > {
9771018 let CallRequest {
9781019 from,
9791020 to,
@@ -989,13 +1030,13 @@ where
9891030
9901031 let data = data. map ( |d| d. 0 ) . unwrap_or_default ( ) ;
9911032
992- let used_gas = match to {
1033+ let ( exit_reason , data , used_gas) = match to {
9931034 Some ( to) => {
9941035 let info = self
9951036 . client
9961037 . runtime_api ( )
9971038 . call (
998- & BlockId :: Hash ( hash ) ,
1039+ & BlockId :: Hash ( best_hash ) ,
9991040 from. unwrap_or_default ( ) ,
10001041 to,
10011042 data,
@@ -1008,16 +1049,14 @@ where
10081049 . map_err ( |err| internal_err ( format ! ( "runtime error: {:?}" , err) ) ) ?
10091050 . map_err ( |err| internal_err ( format ! ( "execution fatal: {:?}" , err) ) ) ?;
10101051
1011- error_on_execution_failure ( & info. exit_reason , & info. value ) ?;
1012-
1013- info. used_gas
1052+ ( info. exit_reason , info. value , info. used_gas )
10141053 }
10151054 None => {
10161055 let info = self
10171056 . client
10181057 . runtime_api ( )
10191058 . create (
1020- & BlockId :: Hash ( hash ) ,
1059+ & BlockId :: Hash ( best_hash ) ,
10211060 from. unwrap_or_default ( ) ,
10221061 data,
10231062 value. unwrap_or_default ( ) ,
@@ -1029,60 +1068,55 @@ where
10291068 . map_err ( |err| internal_err ( format ! ( "runtime error: {:?}" , err) ) ) ?
10301069 . map_err ( |err| internal_err ( format ! ( "execution fatal: {:?}" , err) ) ) ?;
10311070
1032- error_on_execution_failure ( & info. exit_reason , & [ ] ) ?;
1033-
1034- info. used_gas
1071+ ( info. exit_reason , Vec :: new ( ) , info. used_gas )
10351072 }
10361073 } ;
10371074
1038- Ok ( used_gas)
1075+ match exit_reason {
1076+ ExitReason :: Succeed ( _) => Ok ( Some ( used_gas) ) ,
1077+ ExitReason :: Revert ( _) | ExitReason :: Error ( evm:: ExitError :: OutOfGas ) => Ok ( None ) ,
1078+ other => error_on_execution_failure ( & other, & data) . map ( |( ) | Some ( used_gas) ) ,
1079+ }
10391080 } ;
1040- if cfg ! ( feature = "rpc_binary_search_estimate" ) {
1041- let mut lower = U256 :: from ( 21_000 ) ;
1042- // TODO: get a good upper limit, but below U64::max to operation overflow
1043- let mut upper = U256 :: from ( gas_limit) ;
1044- let mut mid = upper;
1045- let mut best = mid;
1046- let mut old_best: U256 ;
1047-
1048- // if the gas estimation depends on the gas limit, then we want to binary
1049- // search until the change is under some threshold. but if not dependent,
1050- // we want to stop immediately.
1051- let mut change_pct = U256 :: from ( 100 ) ;
1052- let threshold_pct = U256 :: from ( 10 ) ;
1053-
1054- // invariant: lower <= mid <= upper
1055- while change_pct > threshold_pct {
1056- let mut test_request = request. clone ( ) ;
1057- test_request. gas = Some ( mid) ;
1058- match calculate_gas_used ( test_request, gas_limit) {
1059- // if Ok -- try to reduce the gas used
1060- Ok ( used_gas) => {
1061- old_best = best;
1062- best = used_gas;
1063- change_pct = ( U256 :: from ( 100 ) * ( old_best - best) ) / old_best;
1064- upper = mid;
1065- mid = ( lower + upper + 1 ) / 2 ;
1066- }
10671081
1068- Err ( err) => {
1069- // if Err == OutofGas, we need more gas
1070- if err. code == ErrorCode :: ServerError ( 0 ) {
1071- lower = mid;
1072- mid = ( lower + upper + 1 ) / 2 ;
1073- if mid == lower {
1074- break ;
1075- }
1076- } else {
1077- // Other errors, return directly
1078- return Err ( err) ;
1079- }
1082+ // verify that the transaction suceed with highest capacity
1083+ let cap = highest;
1084+ let used_gas = executable ( request. clone ( ) , highest) ?. ok_or ( internal_err ( format ! (
1085+ "gas required exceeds allowance {}" ,
1086+ cap
1087+ ) ) ) ?;
1088+
1089+ #[ cfg( not( feature = "rpc_binary_search_estimate" ) ) ]
1090+ {
1091+ Ok ( used_gas)
1092+ }
1093+ #[ cfg( feature = "rpc_binary_search_estimate" ) ]
1094+ {
1095+ // Define the lower bound of the binary search
1096+ const MIN_GAS_PER_TX : U256 = U256 ( [ 21_000 , 0 , 0 , 0 ] ) ;
1097+ let mut lowest = MIN_GAS_PER_TX ;
1098+
1099+ // Start close to the used gas for faster binary search
1100+ let mut mid = std:: cmp:: min ( used_gas * 3 , ( highest + lowest) / 2 ) ;
1101+
1102+ // Execute the binary search and hone in on an executable gas limit.
1103+ let mut previous_highest = highest;
1104+ while ( highest - lowest) > U256 :: one ( ) {
1105+ if executable ( request. clone ( ) , mid) ?. is_some ( ) {
1106+ highest = mid;
1107+ // If the variation in the estimate is less than 10%,
1108+ // then the estimate is considered sufficiently accurate.
1109+ if ( previous_highest - highest) * 10 / previous_highest < U256 :: one ( ) {
1110+ return Ok ( highest) ;
10801111 }
1112+ previous_highest = highest;
1113+ } else {
1114+ lowest = mid;
10811115 }
1116+ mid = ( highest + lowest) / 2 ;
10821117 }
1083- Ok ( best)
1084- } else {
1085- calculate_gas_used ( request, gas_limit)
1118+
1119+ Ok ( highest)
10861120 }
10871121 }
10881122
@@ -1129,14 +1163,15 @@ where
11291163 } ) ?;
11301164
11311165 for txn in ethereum_transactions {
1132- let inner_hash = H256 :: from_slice ( Keccak256 :: digest ( & rlp:: encode ( & txn) ) . as_slice ( ) ) ;
1166+ let inner_hash =
1167+ H256 :: from_slice ( Keccak256 :: digest ( & rlp:: encode ( & txn) ) . as_slice ( ) ) ;
11331168 if hash == inner_hash {
11341169 return Ok ( Some ( transaction_build ( txn, None , None ) ) ) ;
11351170 }
11361171 }
11371172 // Unknown transaction.
11381173 return Ok ( None ) ;
1139- } ,
1174+ }
11401175 } ;
11411176
11421177 let id = match frontier_backend_client:: load_hash :: < B > ( self . backend . as_ref ( ) , hash)
0 commit comments