Skip to content

Commit 1a6a755

Browse files
committed
added vm_error field for transaction block replay
1 parent 33d7113 commit 1a6a755

File tree

5 files changed

+144
-7
lines changed

5 files changed

+144
-7
lines changed

docs/rpc/components/schemas/block-replay.schema.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,7 @@ properties:
7575
description: index of the transaction in the array of transactions
7676
txid:
7777
type: string
78-
description: transaction id
78+
description: transaction id
79+
vm_error:
80+
type: string
81+
description: optional vm error (for runtime failures)

stackslib/src/net/api/blockreplay.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ pub struct RPCReplayedBlockTransaction {
218218
pub execution_cost: ExecutionCost,
219219
/// generated events
220220
pub events: Vec<serde_json::Value>,
221+
/// optional vm error
222+
pub vm_error: Option<String>,
221223
}
222224

223225
impl RPCReplayedBlockTransaction {
@@ -249,6 +251,7 @@ impl RPCReplayedBlockTransaction {
249251
stx_burned: receipt.stx_burned,
250252
execution_cost: receipt.execution_cost.clone(),
251253
events,
254+
vm_error: receipt.vm_error.clone(),
252255
}
253256
}
254257
}

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

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616

1717
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1818

19+
use stacks_common::consts::CHAIN_ID_TESTNET;
1920
use stacks_common::types::chainstate::StacksBlockId;
2021

21-
use crate::chainstate::stacks::Error as ChainError;
22+
use crate::chainstate::stacks::{Error as ChainError, StacksTransaction};
23+
use crate::core::test_util::make_contract_publish;
2224
use crate::net::api::blockreplay;
2325
use crate::net::api::tests::TestRPC;
2426
use crate::net::connection::ConnectionOptions;
2527
use crate::net::httpcore::{StacksHttp, StacksHttpRequest};
2628
use crate::net::test::TestEventObserver;
2729
use crate::net::ProtocolFamily;
30+
use crate::stacks_common::codec::StacksMessageCodec;
2831

2932
#[test]
3033
fn test_try_parse_request() {
@@ -179,3 +182,91 @@ fn test_try_make_response() {
179182
let (preamble, body) = response.destruct();
180183
assert_eq!(preamble.status_code, 401);
181184
}
185+
186+
#[test]
187+
fn test_try_make_response_with_unsuccessful_transaction() {
188+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
189+
190+
let test_observer = TestEventObserver::new();
191+
let rpc_test =
192+
TestRPC::setup_nakamoto_with_boot_plan(function_name!(), &test_observer, |boot_plan| {
193+
let mut tip_transactions: Vec<StacksTransaction> = vec![];
194+
195+
let miner_privk = boot_plan.private_key.clone();
196+
197+
let contract_code = "(broken)";
198+
199+
let deploy_tx_bytes = make_contract_publish(
200+
&miner_privk,
201+
100,
202+
1000,
203+
CHAIN_ID_TESTNET,
204+
&"err-contract",
205+
&contract_code,
206+
);
207+
let deploy_tx =
208+
StacksTransaction::consensus_deserialize(&mut deploy_tx_bytes.as_slice()).unwrap();
209+
210+
tip_transactions.push(deploy_tx);
211+
boot_plan
212+
.with_tip_transactions(tip_transactions)
213+
.with_ignore_transaction_errors(true)
214+
});
215+
216+
let tip_block = test_observer.get_blocks().last().unwrap().clone();
217+
218+
let nakamoto_consensus_hash = rpc_test.consensus_hash.clone();
219+
220+
let mut requests = vec![];
221+
222+
let mut request =
223+
StacksHttpRequest::new_block_replay(addr.clone().into(), &rpc_test.canonical_tip);
224+
// add the authorization header
225+
request.add_header("authorization".into(), "password".into());
226+
requests.push(request);
227+
228+
let mut responses = rpc_test.run(requests);
229+
230+
// got the Nakamoto tip
231+
let response = responses.remove(0);
232+
233+
debug!(
234+
"Response:\n{}\n",
235+
std::str::from_utf8(&response.try_serialize().unwrap()).unwrap()
236+
);
237+
238+
let resp = response.decode_replayed_block().unwrap();
239+
240+
assert_eq!(resp.consensus_hash, nakamoto_consensus_hash);
241+
assert_eq!(resp.consensus_hash, tip_block.metadata.consensus_hash);
242+
243+
assert_eq!(resp.block_hash, tip_block.block.block_hash);
244+
assert_eq!(resp.block_id, tip_block.metadata.index_block_hash());
245+
assert_eq!(resp.parent_block_id, tip_block.parent);
246+
247+
assert_eq!(resp.block_height, tip_block.metadata.stacks_block_height);
248+
249+
assert!(resp.valid_merkle_root);
250+
251+
assert_eq!(resp.transactions.len(), tip_block.receipts.len());
252+
253+
for tx_index in 0..resp.transactions.len() {
254+
assert_eq!(
255+
resp.transactions[tx_index].txid,
256+
tip_block.receipts[tx_index].transaction.txid()
257+
);
258+
assert_eq!(
259+
resp.transactions[tx_index].events.len(),
260+
tip_block.receipts[tx_index].events.len()
261+
);
262+
assert_eq!(
263+
resp.transactions[tx_index].result,
264+
tip_block.receipts[tx_index].result
265+
);
266+
}
267+
268+
assert_eq!(
269+
resp.transactions.last().unwrap().vm_error.clone().unwrap(),
270+
":0:0: use of unresolved function 'broken'"
271+
);
272+
}

stackslib/src/net/tests/inv/nakamoto.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,29 @@ where
515515

516516
plan.initial_balances.append(&mut initial_balances);
517517

518+
if !plan.tip_transactions.is_empty() {
519+
let mut tip_transactions = plan.tip_transactions.clone();
520+
if let Some(tip_tenure) = boot_tenures.last_mut() {
521+
match tip_tenure {
522+
NakamotoBootTenure::Sortition(boot_steps) => match boot_steps.last_mut().unwrap() {
523+
NakamotoBootStep::Block(transactions) => {
524+
transactions.append(&mut tip_transactions)
525+
}
526+
_ => (),
527+
},
528+
NakamotoBootTenure::NoSortition(boot_steps) => {
529+
let boot_steps_len = boot_steps.len();
530+
match boot_steps.get_mut(boot_steps_len - 2).unwrap() {
531+
NakamotoBootStep::Block(transactions) => {
532+
transactions.append(&mut tip_transactions)
533+
}
534+
_ => (),
535+
}
536+
}
537+
}
538+
}
539+
}
540+
518541
let (peer, other_peers) = plan.boot_into_nakamoto_peers(boot_tenures, Some(observer));
519542
(peer, other_peers)
520543
}

stackslib/src/net/tests/mod.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ pub struct NakamotoBootPlan {
104104
pub network_id: u32,
105105
pub txindex: bool,
106106
pub epochs: Option<EpochList<ExecutionCost>>,
107+
pub tip_transactions: Vec<StacksTransaction>,
108+
pub ignore_transaction_errors: bool,
107109
}
108110

109111
impl NakamotoBootPlan {
@@ -124,6 +126,8 @@ impl NakamotoBootPlan {
124126
network_id: TestPeerConfig::default().network_id,
125127
txindex: false,
126128
epochs: None,
129+
tip_transactions: vec![],
130+
ignore_transaction_errors: false,
127131
}
128132
}
129133

@@ -169,6 +173,16 @@ impl NakamotoBootPlan {
169173
self
170174
}
171175

176+
pub fn with_tip_transactions(mut self, tip_transactions: Vec<StacksTransaction>) -> Self {
177+
self.tip_transactions = tip_transactions;
178+
self
179+
}
180+
181+
pub fn with_ignore_transaction_errors(mut self, ignore_transaction_errors: bool) -> Self {
182+
self.ignore_transaction_errors = ignore_transaction_errors;
183+
self
184+
}
185+
172186
pub fn with_test_stackers(mut self, test_stackers: Vec<TestStacker>) -> Self {
173187
self.test_stackers = test_stackers;
174188
self
@@ -891,6 +905,7 @@ impl NakamotoBootPlan {
891905
let test_signers = self.test_signers.clone();
892906
let pox_constants = self.pox_constants.clone();
893907
let test_stackers = self.test_stackers.clone();
908+
let ignore_transaction_errors = self.ignore_transaction_errors;
894909

895910
let (mut peer, mut other_peers) = self.boot_nakamoto_peers(observer);
896911
if boot_plan.is_empty() {
@@ -1191,11 +1206,13 @@ impl NakamotoBootPlan {
11911206
// transactions processed in the same order
11921207
assert_eq!(receipt.transaction.txid(), tx.txid());
11931208
// no CheckErrors
1194-
assert!(
1195-
receipt.vm_error.is_none(),
1196-
"Receipt had a CheckErrors: {:?}",
1197-
&receipt
1198-
);
1209+
if !ignore_transaction_errors {
1210+
assert!(
1211+
receipt.vm_error.is_none(),
1212+
"Receipt had a CheckErrors: {:?}",
1213+
&receipt
1214+
);
1215+
}
11991216
// transaction was not aborted post-hoc
12001217
assert!(!receipt.post_condition_aborted);
12011218
}

0 commit comments

Comments
 (0)