Skip to content
This repository has been archived by the owner on Aug 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request stacks-network#4754 from stacks-network/fix/4752
Browse files Browse the repository at this point in the history
Fix/4752
  • Loading branch information
jcnelson authored May 10, 2024
2 parents 4afa9ef + 05bf8a8 commit 75341d9
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 47 deletions.
78 changes: 64 additions & 14 deletions stackslib/src/net/api/getstxtransfercost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,39 @@

use std::io::{Read, Write};

use clarity::vm::costs::ExecutionCost;
use regex::{Captures, Regex};
use stacks_common::types::chainstate::{
BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey,
};
use stacks_common::types::net::PeerHost;
use stacks_common::types::StacksPublicKeyBuffer;
use stacks_common::util::hash::{Hash160, Sha256Sum};
use url::form_urlencoded;

use crate::burnchains::affirmation::AffirmationMap;
use crate::burnchains::Txid;
use crate::chainstate::burn::db::sortdb::SortitionDB;
use crate::chainstate::stacks::db::blocks::MINIMUM_TX_FEE_RATE_PER_BYTE;
use crate::chainstate::stacks::db::StacksChainState;
use crate::core::mempool::MemPoolDB;
use crate::net::api::postfeerate::RPCPostFeeRateRequestHandler;
use crate::net::http::{
parse_json, Error, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse,
HttpResponseContents, HttpResponsePayload, HttpResponsePreamble,
parse_json, Error, HttpBadRequest, HttpRequest, HttpRequestContents, HttpRequestPreamble,
HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble,
};
use crate::net::httpcore::{
HttpPreambleExtensions, RPCRequestHandler, StacksHttpRequest, StacksHttpResponse,
};
use crate::net::p2p::PeerNetwork;
use crate::net::{Error as NetError, StacksNodeState};
use crate::net::{Error as NetError, HttpServerError, StacksNodeState};
use crate::version_string;

pub(crate) const SINGLESIG_TX_TRANSFER_LEN: u64 = 180;

#[derive(Clone)]
pub struct RPCGetStxTransferCostRequestHandler {}

impl RPCGetStxTransferCostRequestHandler {
pub fn new() -> Self {
Self {}
Expand Down Expand Up @@ -74,7 +80,7 @@ impl HttpRequest for RPCGetStxTransferCostRequestHandler {
) -> Result<HttpRequestContents, Error> {
if preamble.get_content_length() != 0 {
return Err(Error::DecodeError(
"Invalid Http request: expected 0-length body for GetInfo".to_string(),
"Invalid Http request: expected 0-length body".to_string(),
));
}
Ok(HttpRequestContents::new().query_string(query))
Expand All @@ -92,9 +98,57 @@ impl RPCRequestHandler for RPCGetStxTransferCostRequestHandler {
_contents: HttpRequestContents,
node: &mut StacksNodeState,
) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
// todo -- need to actually estimate the cost / length for token transfers
// right now, it just uses the minimum.
let fee = MINIMUM_TX_FEE_RATE_PER_BYTE;
// NOTE: The estimated length isn't needed per se because we're returning a fee rate, but
// we do need an absolute length to use the estimator (so supply a common one).
let estimated_len = SINGLESIG_TX_TRANSFER_LEN;

let fee_resp = node.with_node_state(|_network, sortdb, _chainstate, _mempool, rpc_args| {
let tip = self.get_canonical_burn_chain_tip(&preamble, sortdb)?;
let stacks_epoch = self.get_stacks_epoch(&preamble, sortdb, tip.block_height)?;

if let Some((_, fee_estimator, metric)) = rpc_args.get_estimators_ref() {
// STX transfer transactions have zero runtime cost
let estimated_cost = ExecutionCost::zero();
let estimations =
RPCPostFeeRateRequestHandler::estimate_tx_fee_from_cost_and_length(
&preamble,
fee_estimator,
metric,
estimated_cost,
estimated_len,
stacks_epoch,
)?
.estimations;
if estimations.len() != 3 {
// logic bug, but treat as runtime error
return Err(StacksHttpResponse::new_error(
&preamble,
&HttpServerError::new(
"Logic error in fee estimation: did not get three estimates".into(),
),
));
}

// safety -- checked estimations.len() == 3 above
let median_estimation = &estimations[1];

// NOTE: this returns the fee _rate_
Ok(median_estimation.fee / estimated_len)
} else {
// unlike `POST /v2/fees/transaction`, this method can't fail due to the
// unavailability of cost estimation, so just assume the minimum fee.
debug!("Fee and cost estimation not configured on this stacks node");
Ok(MINIMUM_TX_FEE_RATE_PER_BYTE)
}
});

let fee = match fee_resp {
Ok(fee) => fee,
Err(response) => {
return response.try_into_contents().map_err(NetError::from);
}
};

let mut preamble = HttpResponsePreamble::ok_json(&preamble);
preamble.set_canonical_stacks_tip_height(Some(node.canonical_stacks_tip_height()));
let body = HttpResponseContents::try_from_json(&fee)?;
Expand All @@ -116,13 +170,9 @@ impl HttpResponse for RPCGetStxTransferCostRequestHandler {

impl StacksHttpRequest {
pub fn new_get_stx_transfer_cost(host: PeerHost) -> StacksHttpRequest {
StacksHttpRequest::new_for_peer(
host,
"GET".into(),
"/v2/fees/transfer".into(),
HttpRequestContents::new(),
)
.expect("FATAL: failed to construct request from infallible data")
let mut contents = HttpRequestContents::new();
StacksHttpRequest::new_for_peer(host, "GET".into(), "/v2/fees/transfer".into(), contents)
.expect("FATAL: failed to construct request from infallible data")
}
}

Expand Down
86 changes: 53 additions & 33 deletions stackslib/src/net/api/postfeerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ use crate::chainstate::stacks::db::blocks::MINIMUM_TX_FEE_RATE_PER_BYTE;
use crate::chainstate::stacks::db::StacksChainState;
use crate::chainstate::stacks::TransactionPayload;
use crate::core::mempool::MemPoolDB;
use crate::cost_estimates::FeeRateEstimate;
use crate::core::StacksEpoch;
use crate::cost_estimates::metrics::CostMetric;
use crate::cost_estimates::{CostEstimator, FeeEstimator, FeeRateEstimate};
use crate::net::http::{
parse_json, Error, HttpBadRequest, HttpContentType, HttpNotFound, HttpRequest,
HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents,
Expand Down Expand Up @@ -92,13 +94,56 @@ pub struct RPCPostFeeRateRequestHandler {
pub estimated_len: Option<u64>,
pub transaction_payload: Option<TransactionPayload>,
}

impl RPCPostFeeRateRequestHandler {
pub fn new() -> Self {
Self {
estimated_len: None,
transaction_payload: None,
}
}

/// Estimate a transaction fee, given its execution cost estimation and length estimation
/// and cost estimators.
/// Returns Ok(fee structure) on success
/// Returns Err(HTTP response) on error
pub fn estimate_tx_fee_from_cost_and_length(
preamble: &HttpRequestPreamble,
fee_estimator: &dyn FeeEstimator,
metric: &dyn CostMetric,
estimated_cost: ExecutionCost,
estimated_len: u64,
stacks_epoch: StacksEpoch,
) -> Result<RPCFeeEstimateResponse, StacksHttpResponse> {
let scalar_cost =
metric.from_cost_and_len(&estimated_cost, &stacks_epoch.block_limit, estimated_len);
let fee_rates = fee_estimator.get_rate_estimates().map_err(|e| {
StacksHttpResponse::new_error(
&preamble,
&HttpBadRequest::new(format!(
"Estimator RPC endpoint failed to estimate fees for tx: {:?}",
&e
)),
)
})?;

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;
}
}

Ok(RPCFeeEstimateResponse {
estimated_cost,
estimations,
estimated_cost_scalar: scalar_cost,
cost_scalar_change_by_byte: metric.change_per_byte(),
})
}
}

/// Decode the HTTP request
Expand Down Expand Up @@ -206,39 +251,14 @@ impl RPCRequestHandler for RPCPostFeeRateRequestHandler {
)
})?;

let scalar_cost = metric.from_cost_and_len(
&estimated_cost,
&stacks_epoch.block_limit,
estimated_len,
);
let fee_rates = fee_estimator.get_rate_estimates().map_err(|e| {
StacksHttpResponse::new_error(
&preamble,
&HttpBadRequest::new(format!(
"Estimator RPC endpoint failed to estimate fees for tx {}: {:?}",
&tx.name(),
&e
)),
)
})?;

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;
}
}

Ok(RPCFeeEstimateResponse {
Self::estimate_tx_fee_from_cost_and_length(
&preamble,
fee_estimator,
metric,
estimated_cost,
estimations,
estimated_cost_scalar: scalar_cost,
cost_scalar_change_by_byte: metric.change_per_byte(),
})
estimated_len,
stacks_epoch,
)
} else {
debug!("Fee and cost estimation not configured on this stacks node");
Err(StacksHttpResponse::new_error(
Expand Down
3 changes: 3 additions & 0 deletions stackslib/src/net/api/tests/getstxtransfercost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use stacks_common::types::Address;
use super::test_rpc;
use crate::chainstate::stacks::db::blocks::MINIMUM_TX_FEE_RATE_PER_BYTE;
use crate::core::BLOCK_LIMIT_MAINNET_21;
use crate::net::api::getstxtransfercost::SINGLESIG_TX_TRANSFER_LEN;
use crate::net::api::*;
use crate::net::connection::ConnectionOptions;
use crate::net::httpcore::{
Expand Down Expand Up @@ -67,6 +68,7 @@ fn test_try_make_response() {

let mut responses = test_rpc(function_name!(), vec![request]);
assert_eq!(responses.len(), 1);
responses.reverse();

let response = responses.pop().unwrap();
debug!(
Expand All @@ -80,5 +82,6 @@ fn test_try_make_response() {
);

let fee_rate = response.decode_stx_transfer_fee().unwrap();
debug!("fee_rate = {:?}", &fee_rate);
assert_eq!(fee_rate, MINIMUM_TX_FEE_RATE_PER_BYTE);
}

0 comments on commit 75341d9

Please sign in to comment.