Skip to content

Commit 9b48330

Browse files
authored
Merge pull request #5793 from stacks-network/fix/4145
Fix: well-formed JSON error responses on /v2/fees/transaction
2 parents 91a1398 + 8ad0a2b commit 9b48330

File tree

7 files changed

+224
-34
lines changed

7 files changed

+224
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1313

1414
### Fixed
1515

16+
- Error responses to /v2/transactions/fees are once again expressed as JSON ([#4145](https://github.com/stacks-network/stacks-core/issues/4145)).
17+
1618
## [3.1.0.0.5]
1719

1820
### Added

stackslib/src/cost_estimates/tests/fee_rate_fuzzer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::cost_estimates::fee_rate_fuzzer::FeeRateFuzzer;
1515
use crate::cost_estimates::tests::common::make_block_receipt;
1616
use crate::cost_estimates::{EstimatorError, FeeEstimator, FeeRateEstimate};
1717

18-
struct ConstantFeeEstimator {}
18+
pub struct ConstantFeeEstimator {}
1919

2020
/// Returns a constant fee rate estimate.
2121
impl FeeEstimator for ConstantFeeEstimator {

stackslib/src/net/api/postfeerate.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::io::{Read, Write};
1818

1919
use clarity::vm::costs::ExecutionCost;
2020
use regex::{Captures, Regex};
21+
use serde_json::json;
2122
use stacks_common::codec::{StacksMessageCodec, MAX_PAYLOAD_LEN};
2223
use stacks_common::types::chainstate::{
2324
BlockHeaderHash, ConsensusHash, StacksBlockId, StacksPublicKey,
@@ -118,13 +119,7 @@ impl RPCPostFeeRateRequestHandler {
118119
let scalar_cost =
119120
metric.from_cost_and_len(&estimated_cost, &stacks_epoch.block_limit, estimated_len);
120121
let fee_rates = fee_estimator.get_rate_estimates().map_err(|e| {
121-
StacksHttpResponse::new_error(
122-
preamble,
123-
&HttpBadRequest::new(format!(
124-
"Estimator RPC endpoint failed to estimate fees for tx: {:?}",
125-
&e
126-
)),
127-
)
122+
StacksHttpResponse::new_error(preamble, &HttpBadRequest::new_json(e.into_json()))
128123
})?;
129124

130125
let mut estimations = RPCFeeEstimate::estimate_fees(scalar_cost, fee_rates).to_vec();
@@ -243,11 +238,7 @@ impl RPCRequestHandler for RPCPostFeeRateRequestHandler {
243238
.map_err(|e| {
244239
StacksHttpResponse::new_error(
245240
&preamble,
246-
&HttpBadRequest::new(format!(
247-
"Estimator RPC endpoint failed to estimate tx {}: {:?}",
248-
&tx.name(),
249-
&e
250-
)),
241+
&HttpBadRequest::new_json(e.into_json()),
251242
)
252243
})?;
253244

@@ -263,9 +254,9 @@ impl RPCRequestHandler for RPCPostFeeRateRequestHandler {
263254
debug!("Fee and cost estimation not configured on this stacks node");
264255
Err(StacksHttpResponse::new_error(
265256
&preamble,
266-
&HttpBadRequest::new(
267-
"Fee estimation not supported on this node".to_string(),
268-
),
257+
&HttpBadRequest::new_json(json!(
258+
"Fee estimation not supported on this node"
259+
)),
269260
))
270261
}
271262
});

stackslib/src/net/api/tests/mod.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
use std::net::SocketAddr;
18+
use std::sync::{Arc, Mutex};
1819

1920
use clarity::vm::costs::ExecutionCost;
2021
use clarity::vm::types::{QualifiedContractIdentifier, StacksAddressExtensions};
@@ -46,7 +47,7 @@ use crate::net::db::PeerDB;
4647
use crate::net::httpcore::{StacksHttpRequest, StacksHttpResponse};
4748
use crate::net::relay::Relayer;
4849
use crate::net::rpc::ConversationHttp;
49-
use crate::net::test::{TestEventObserver, TestPeer, TestPeerConfig};
50+
use crate::net::test::{RPCHandlerArgsType, TestEventObserver, TestPeer, TestPeerConfig};
5051
use crate::net::tests::inv::nakamoto::make_nakamoto_peers_from_invs_ext;
5152
use crate::net::{
5253
Attachment, AttachmentInstance, MemPoolEventDispatcher, RPCHandlerArgs, StackerDBConfig,
@@ -226,10 +227,28 @@ pub struct TestRPC<'a> {
226227

227228
impl<'a> TestRPC<'a> {
228229
pub fn setup(test_name: &str) -> TestRPC<'a> {
229-
Self::setup_ex(test_name, true)
230+
Self::setup_ex(test_name, true, None, None)
230231
}
231232

232-
pub fn setup_ex(test_name: &str, process_microblock: bool) -> TestRPC<'a> {
233+
pub fn setup_with_rpc_args(
234+
test_name: &str,
235+
rpc_handler_args_opt_1: Option<RPCHandlerArgsType>,
236+
rpc_handler_args_opt_2: Option<RPCHandlerArgsType>,
237+
) -> TestRPC<'a> {
238+
Self::setup_ex(
239+
test_name,
240+
true,
241+
rpc_handler_args_opt_1,
242+
rpc_handler_args_opt_2,
243+
)
244+
}
245+
246+
pub fn setup_ex(
247+
test_name: &str,
248+
process_microblock: bool,
249+
rpc_handler_args_opt_1: Option<RPCHandlerArgsType>,
250+
rpc_handler_args_opt_2: Option<RPCHandlerArgsType>,
251+
) -> TestRPC<'a> {
233252
// ST2DS4MSWSGJ3W9FBC6BVT0Y92S345HY8N3T6AV7R
234253
let privk1 = StacksPrivateKey::from_hex(
235254
"9f1f85a512a96a244e4c0d762788500687feb97481639572e3bffbd6860e6ab001",
@@ -317,6 +336,9 @@ impl<'a> TestRPC<'a> {
317336
let mut peer_1 = TestPeer::new(peer_1_config);
318337
let mut peer_2 = TestPeer::new(peer_2_config);
319338

339+
peer_1.rpc_handler_args = rpc_handler_args_opt_1;
340+
peer_2.rpc_handler_args = rpc_handler_args_opt_2;
341+
320342
// mine one block with a contract in it
321343
// first the coinbase
322344
// make a coinbase for this miner
@@ -976,7 +998,11 @@ impl<'a> TestRPC<'a> {
976998
}
977999

9781000
{
979-
let mut rpc_args = RPCHandlerArgs::default();
1001+
let mut rpc_args = peer_1
1002+
.rpc_handler_args
1003+
.as_ref()
1004+
.map(|args_type| args_type.instantiate())
1005+
.unwrap_or(RPCHandlerArgsType::make_default());
9801006
rpc_args.event_observer = event_observer;
9811007
let mut node_state = StacksNodeState::new(
9821008
&mut peer_1.network,
@@ -1020,7 +1046,11 @@ impl<'a> TestRPC<'a> {
10201046
}
10211047

10221048
{
1023-
let mut rpc_args = RPCHandlerArgs::default();
1049+
let mut rpc_args = peer_2
1050+
.rpc_handler_args
1051+
.as_ref()
1052+
.map(|args_type| args_type.instantiate())
1053+
.unwrap_or(RPCHandlerArgsType::make_default());
10241054
rpc_args.event_observer = event_observer;
10251055
let mut node_state = StacksNodeState::new(
10261056
&mut peer_2.network,
@@ -1076,7 +1106,11 @@ impl<'a> TestRPC<'a> {
10761106
let mut peer_1_stacks_node = peer_1.stacks_node.take().unwrap();
10771107
let mut peer_1_mempool = peer_1.mempool.take().unwrap();
10781108

1079-
let rpc_args = RPCHandlerArgs::default();
1109+
let rpc_args = peer_1
1110+
.rpc_handler_args
1111+
.as_ref()
1112+
.map(|args_type| args_type.instantiate())
1113+
.unwrap_or(RPCHandlerArgsType::make_default());
10801114
let mut node_state = StacksNodeState::new(
10811115
&mut peer_1.network,
10821116
&peer_1_sortdb,

stackslib/src/net/api/tests/postfeerate.rs

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,29 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
18+
use std::sync::Arc;
1819

1920
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, StacksAddressExtensions};
2021
use clarity::vm::{ClarityName, ContractName, Value};
2122
use stacks_common::types::chainstate::StacksAddress;
2223
use stacks_common::types::net::PeerHost;
2324
use stacks_common::types::Address;
24-
use stacks_common::util::hash::to_hex;
25+
use stacks_common::util::hash::{to_hex, Sha256Sum};
2526

2627
use super::test_rpc;
2728
use crate::chainstate::stacks::TransactionPayload;
2829
use crate::core::BLOCK_LIMIT_MAINNET_21;
30+
use crate::cost_estimates::metrics::UnitMetric;
31+
use crate::cost_estimates::tests::fee_rate_fuzzer::ConstantFeeEstimator;
32+
use crate::cost_estimates::UnitEstimator;
33+
use crate::net::api::tests::TestRPC;
2934
use crate::net::api::*;
3035
use crate::net::connection::ConnectionOptions;
3136
use crate::net::httpcore::{
3237
HttpRequestContentsExtensions, RPCRequestHandler, StacksHttp, StacksHttpRequest,
3338
};
34-
use crate::net::{ProtocolFamily, TipRequest};
39+
use crate::net::test::RPCHandlerArgsType;
40+
use crate::net::{ProtocolFamily, RPCHandlerArgs, TipRequest};
3541

3642
#[test]
3743
fn test_try_parse_request() {
@@ -89,17 +95,50 @@ fn test_try_make_response() {
8995
TransactionPayload::new_contract_call(sender_addr, "hello-world", "add-unit", vec![])
9096
.unwrap();
9197

98+
// case 1: no fee estimates
9299
let mut requests = vec![];
93100
let request = StacksHttpRequest::new_post_fee_rate(
94-
addr.into(),
101+
addr.clone().into(),
102+
postfeerate::FeeRateEstimateRequestBody {
103+
estimated_len: Some(123),
104+
transaction_payload: to_hex(&tx_payload.serialize_to_vec()),
105+
},
106+
);
107+
requests.push(request);
108+
109+
let test_rpc = TestRPC::setup(function_name!());
110+
let mut responses = test_rpc.run(requests);
111+
112+
let response = responses.remove(0);
113+
debug!(
114+
"Response:\n{}\n",
115+
std::str::from_utf8(&response.try_serialize().unwrap()).unwrap()
116+
);
117+
118+
let (preamble, body) = response.destruct();
119+
let body_json: serde_json::Value = body.try_into().unwrap();
120+
121+
// get back a JSON string and a 400
122+
assert_eq!(preamble.status_code, 400);
123+
debug!("Response JSON no estimator: {}", &body_json);
124+
125+
// case 2: no estimate available
126+
let mut requests = vec![];
127+
let request = StacksHttpRequest::new_post_fee_rate(
128+
addr.clone().into(),
95129
postfeerate::FeeRateEstimateRequestBody {
96130
estimated_len: Some(123),
97131
transaction_payload: to_hex(&tx_payload.serialize_to_vec()),
98132
},
99133
);
100134
requests.push(request);
101135

102-
let mut responses = test_rpc(function_name!(), requests);
136+
let test_rpc = TestRPC::setup_with_rpc_args(
137+
function_name!(),
138+
Some(RPCHandlerArgsType::Null),
139+
Some(RPCHandlerArgsType::Null),
140+
);
141+
let mut responses = test_rpc.run(requests);
103142

104143
let response = responses.remove(0);
105144
debug!(
@@ -108,5 +147,46 @@ fn test_try_make_response() {
108147
);
109148

110149
let (preamble, body) = response.destruct();
150+
let body_json: serde_json::Value = body.try_into().unwrap();
151+
152+
// get back a JSON object and a 400
111153
assert_eq!(preamble.status_code, 400);
154+
debug!("Response JSON no estimate fee: {}", &body_json);
155+
assert_eq!(
156+
body_json.get("reason").unwrap().as_str().unwrap(),
157+
"NoEstimateAvailable"
158+
);
159+
assert!(body_json.get("error").is_some());
160+
assert!(body_json.get("reason_data").is_some());
161+
162+
// case 3: get an estimate
163+
let mut requests = vec![];
164+
let request = StacksHttpRequest::new_post_fee_rate(
165+
addr.clone().into(),
166+
postfeerate::FeeRateEstimateRequestBody {
167+
estimated_len: Some(123),
168+
transaction_payload: to_hex(&tx_payload.serialize_to_vec()),
169+
},
170+
);
171+
requests.push(request);
172+
173+
let test_rpc = TestRPC::setup_with_rpc_args(
174+
function_name!(),
175+
Some(RPCHandlerArgsType::Unit),
176+
Some(RPCHandlerArgsType::Unit),
177+
);
178+
let mut responses = test_rpc.run(requests);
179+
180+
let response = responses.remove(0);
181+
debug!(
182+
"Response:\n{}\n",
183+
std::str::from_utf8(&response.try_serialize().unwrap()).unwrap()
184+
);
185+
186+
let (preamble, body) = response.destruct();
187+
let body_json: serde_json::Value = body.try_into().unwrap();
188+
189+
// get back a JSON object and a 200
190+
assert_eq!(preamble.status_code, 200);
191+
debug!("Response JSON success: {}", &body_json);
112192
}

stackslib/src/net/api/tests/postmicroblock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ fn test_try_parse_request() {
102102
fn test_try_make_response() {
103103
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
104104

105-
let test_rpc = TestRPC::setup_ex(function_name!(), false);
105+
let test_rpc = TestRPC::setup_ex(function_name!(), false, None, None);
106106
let mblock = test_rpc.next_microblock.clone().unwrap();
107107

108108
let mut requests = vec![];

0 commit comments

Comments
 (0)