use estimations array, address PR feedback, check minimum relay fees

This commit is contained in:
Aaron Blankstein
2021-10-20 09:47:35 -05:00
parent ced4a64946
commit d7da3014a0
7 changed files with 122 additions and 59 deletions

View File

@@ -8,14 +8,18 @@
"write_length": 1020
},
"estimated_cost_scalar": 14,
"estimated_fee_rates": [
1.2410714285714286,
8.958333333333332,
10
],
"estimated_fees": [
17,
125,
140
"estimations": [
{
"fee": 17,
"fee_rate": 1.2410714285714286
},
{
"fee": 125,
"fee_rate": 8.958333333333332
},
{
"fee": 140,
"fee_rate": 10
}
]
}

View File

@@ -24,16 +24,18 @@
"write_length": { "type": "integer" }
}
},
"estimated_fee_rates": {
"estimations": {
"type": "array",
"items": {
"type": "number"
}
},
"estimated_fees": {
"type": "array",
"items": {
"type": "integer"
"type": "object",
"properties": {
"fee_rate": {
"type": "number"
},
"fee": {
"type": "number"
}
}
}
}
}

View File

@@ -282,7 +282,7 @@ paths:
tags:
- Fees
description: |
Get an estimated fee for that supplied transaction. This
Get an estimated fee for the supplied transaction. This
estimates the execution cost of the transaction, the current
fee rate of the network, and returns estimates for fee
amounts.
@@ -290,30 +290,53 @@ paths:
* `transaction_payload` is a hex-encoded serialization of
the TransactionPayload for the transaction.
* `estimated_len` is an optional argument that provides the
endpoint
endpoint with an estimation of the final length (in bytes)
of the transaction, including any post-conditions and
signatures
If the node cannot provide an estimate for the transaction
(e.g., if the node has never seen a contract-call for the
given contract and function) or if estimation is not
configured on this node, a 400 response is returned.
The 400 response will be a JSON error containing a `reason`
field which can be one of the following:
* `DatabaseError` - this Stacks node has had an internal
database error while trying to estimate the costs of the
supplied transaction.
* `NoEstimateAvailable` - this Stacks node has not seen this
kind of contract-call before, and it cannot provide an
estimate yet.
* `CostEstimationDisabled` - this Stacks node does not perform
fee or cost estimation, and it cannot respond on this
endpoint.
The 200 response contains the following data:
* `estimated_cost` - the estimated multi-dimensional cost of
executing the Clarity VM on the provided transaction.
* `estimated_cost_scalar` - an integer that captures the total
proportion of the block limit that a transaction would be
estimated to consume. This value is multiplied by a fee rate
to suggest the total fee amount to be paid by the node. This
* `estimated_cost_scalar` - a unitless integer that captures
the total proportion of the block limit that a transaction
would be estimated to consume. In order to compute an
estimate of total fee amount for the transaction, this value
is multiplied by the estimated fee rate. This value
incorporates the estimated transaction length.
* `cost_scalar_change_by_byte` - a float value that indicates how
much the `estimated_cost_scalar` value would increase for every
additional byte in the final transaction.
* `estimated_fee_rates` - three estimated values for the current
fee rates in the network
* `estimated_fees` - three estimated values for the total fee that
the given transaction should pay. These values are the result of
computing: `estimated_fee_rates` x `estimated_cost_scalar`
* `estimations` - an array of estimated fee rates and total fees to
pay in microSTX for the transaction. This array provides a range of
estimates (default: 3) that may be used. Each element of the array
contains the following fields:
* `fee_rate` - the estimated value for the current fee
rates in the network
* `fee` - the estimated value for the total fee in
microSTX that the given transaction should pay. These
values are the result of computing:
`fee_rate` x `estimated_cost_scalar`.
If the estimated fees are less than the minimum relay
fee `(1 ustx x estimated_len)`, then that minimum relay
fee will be returned here instead.
Note: If the final transaction's byte size is larger than

View File

@@ -192,7 +192,7 @@ impl EstimatorError {
Some(json!({"message": self.to_string()})),
),
EstimatorError::SqliteError(_) => {
("DatavaseError", Some(json!({"message": self.to_string()})))
("DatabaseError", Some(json!({"message": self.to_string()})))
}
};
let mut result = json!({

View File

@@ -1029,23 +1029,27 @@ pub struct RPCPoxInfoData {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCFeeEstimate {
pub high: u64,
pub middle: u64,
pub low: u64,
pub fee_rate: f64,
pub fee: u64,
}
impl RPCFeeEstimate {
pub fn estimate_fees(scalar: u64, fee_rates: FeeRateEstimate) -> RPCFeeEstimate {
let estimated_fees_f64 = fee_rates * (scalar as f64);
RPCFeeEstimate {
high: estimated_fees_f64.high as u64,
middle: estimated_fees_f64.middle as u64,
low: estimated_fees_f64.low as u64,
}
}
pub fn to_vec(self) -> Vec<u64> {
vec![self.low, self.middle, self.high]
pub fn estimate_fees(scalar: u64, fee_rates: FeeRateEstimate) -> Vec<RPCFeeEstimate> {
let estimated_fees_f64 = fee_rates.clone() * (scalar as f64);
vec![
RPCFeeEstimate {
fee: estimated_fees_f64.low as u64,
fee_rate: fee_rates.low,
},
RPCFeeEstimate {
fee: estimated_fees_f64.middle as u64,
fee_rate: fee_rates.middle,
},
RPCFeeEstimate {
fee: estimated_fees_f64.high as u64,
fee_rate: fee_rates.high,
},
]
}
}
@@ -1053,8 +1057,7 @@ impl RPCFeeEstimate {
pub struct RPCFeeEstimateResponse {
pub estimated_cost: ExecutionCost,
pub estimated_cost_scalar: u64,
pub estimated_fees: Vec<u64>,
pub estimated_fee_rates: Vec<f64>,
pub estimations: Vec<RPCFeeEstimate>,
pub cost_scalar_change_by_byte: f64,
}

View File

@@ -1631,16 +1631,22 @@ impl ConversationHttp {
}
};
let estimated_fees =
RPCFeeEstimate::estimate_fees(scalar_cost, fee_rates.clone()).to_vec();
let mut estimations = RPCFeeEstimate::estimate_fees(scalar_cost, fee_rates).to_vec();
let minimum_fee = estimated_len * MINIMUM_TX_FEE_RATE_PER_BYTE;
for estimate in estimations.iter_mut() {
if estimate.fee < minimum_fee {
estimate.fee = minimum_fee;
}
}
let response = HttpResponseType::TransactionFeeEstimation(
response_metadata,
RPCFeeEstimateResponse {
estimated_cost,
estimated_fees,
estimations,
estimated_cost_scalar: scalar_cost,
estimated_fee_rates: fee_rates.to_vec(),
cost_scalar_change_by_byte: metric.change_per_byte(),
},
);

View File

@@ -5,6 +5,7 @@ use std::sync::Mutex;
use reqwest;
use stacks::burnchains::Address;
use stacks::chainstate::stacks::db::blocks::MINIMUM_TX_FEE_RATE_PER_BYTE;
use stacks::chainstate::stacks::{
db::blocks::MemPoolRejection, db::StacksChainState, StacksPrivateKey, StacksTransaction,
};
@@ -857,13 +858,19 @@ fn integration_test_get_info() {
// the estimated scalar should still be non-zero, because the length of the tx goes into this field.
assert!(res.get("estimated_cost_scalar").unwrap().as_u64().unwrap() > 0);
let estimated_fee_rates = res.get("estimated_fee_rates").expect("Should have an estimated_fee_rates field")
.as_array()
.expect("Fees should be array");
let estimated_fees = res.get("estimated_fees").expect("Should have an estimated_fees field")
let estimations = res.get("estimations").expect("Should have an estimations field")
.as_array()
.expect("Fees should be array");
let estimated_fee_rates: Vec<_> = estimations
.iter()
.map(|x| x.get("fee_rate").expect("Should have fee_rate field"))
.collect();
let estimated_fees: Vec<_> = estimations
.iter()
.map(|x| x.get("fee").expect("Should have fee field"))
.collect();
assert!(estimated_fee_rates.len() == 3, "Fee rates should be length 3 array");
assert!(estimated_fees.len() == 3, "Fees should be length 3 array");
@@ -902,13 +909,19 @@ fn integration_test_get_info() {
let estimated_cost_scalar = res.get("estimated_cost_scalar").unwrap().as_u64().unwrap();
assert!(estimated_cost_scalar > 0);
let estimated_fee_rates = res.get("estimated_fee_rates").expect("Should have an estimated_fee_rates field")
.as_array()
.expect("Fees should be array");
let estimated_fees = res.get("estimated_fees").expect("Should have an estimated_fees field")
let estimations = res.get("estimations").expect("Should have an estimations field")
.as_array()
.expect("Fees should be array");
let estimated_fee_rates: Vec<_> = estimations
.iter()
.map(|x| x.get("fee_rate").expect("Should have fee_rate field"))
.collect();
let estimated_fees: Vec<_> = estimations
.iter()
.map(|x| x.get("fee").expect("Should have fee field"))
.collect();
assert!(estimated_fee_rates.len() == 3, "Fee rates should be length 3 array");
assert!(estimated_fees.len() == 3, "Fees should be length 3 array");
@@ -922,7 +935,8 @@ fn integration_test_get_info() {
let payload_data = tx_payload.serialize_to_vec();
let payload_hex = to_hex(&payload_data);
let body = json!({ "transaction_payload": payload_hex.clone(), "estimated_len": 1550 });
let estimated_len = 1550;
let body = json!({ "transaction_payload": payload_hex.clone(), "estimated_len": estimated_len });
info!("POST body\n {}", body);
let res = client.post(&path)
@@ -947,16 +961,27 @@ fn integration_test_get_info() {
assert!(estimated_cost_scalar > 0);
assert!(new_estimated_cost_scalar > estimated_cost_scalar, "New scalar estimate should be higher because of the tx length increase");
let new_estimated_fees = res.get("estimated_fees").expect("Should have an estimated_fees field")
let new_estimations = res.get("estimations").expect("Should have an estimations field")
.as_array()
.expect("Fees should be array");
let new_estimated_fees: Vec<_> = new_estimations
.iter()
.map(|x| x.get("fee").expect("Should have fee field"))
.collect();
let minimum_relay_fee = estimated_len * MINIMUM_TX_FEE_RATE_PER_BYTE;
assert!(new_estimated_fees[2].as_u64().unwrap() >= estimated_fees[2].as_u64().unwrap(),
"Supplying an estimated tx length should increase the estimated fees");
assert!(new_estimated_fees[0].as_u64().unwrap() >= estimated_fees[0].as_u64().unwrap(),
"Supplying an estimated tx length should increase the estimated fees");
assert!(new_estimated_fees[1].as_u64().unwrap() >= estimated_fees[1].as_u64().unwrap(),
"Supplying an estimated tx length should increase the estimated fees");
for estimate in new_estimated_fees.iter() {
assert!(estimate.as_u64().unwrap() >= minimum_relay_fee,
"The estimated fees must always be greater than minimum_relay_fee");
}
},
_ => {},
}