Skip to content

Commit f8d02cd

Browse files
authored
Merge pull request #6575 from rdeioris/fix/block_replay_unsuccessful
Add vm_error field for transactions in Block Replay
2 parents 2225979 + 24911bc commit f8d02cd

File tree

5 files changed

+148
-7
lines changed

5 files changed

+148
-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, "null"]
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
@@ -221,6 +221,8 @@ pub struct RPCReplayedBlockTransaction {
221221
pub execution_cost: ExecutionCost,
222222
/// generated events
223223
pub events: Vec<serde_json::Value>,
224+
/// optional vm error
225+
pub vm_error: Option<String>,
224226
}
225227

226228
impl RPCReplayedBlockTransaction {
@@ -252,6 +254,7 @@ impl RPCReplayedBlockTransaction {
252254
stx_burned: receipt.stx_burned,
253255
execution_cost: receipt.execution_cost.clone(),
254256
events,
257+
vm_error: receipt.vm_error.clone(),
255258
}
256259
}
257260
}

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,31 @@ 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+
// when NakamotoBootTenure::NoSortition is in place we have every NakamotoBootStep::Block
531+
// followed by NakamotoBootStep::TenureExtend (this is why we index by boot_steps_len - 2)
532+
match boot_steps.get_mut(boot_steps_len - 2).unwrap() {
533+
NakamotoBootStep::Block(transactions) => {
534+
transactions.append(&mut tip_transactions)
535+
}
536+
_ => (),
537+
}
538+
}
539+
}
540+
}
541+
}
542+
518543
let (peer, other_peers) = plan.boot_into_nakamoto_peers(boot_tenures, Some(observer));
519544
(peer, other_peers)
520545
}

stackslib/src/net/tests/mod.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ pub struct NakamotoBootPlan {
103103
pub network_id: u32,
104104
pub txindex: bool,
105105
pub epochs: Option<EpochList<ExecutionCost>>,
106+
/// Additional transactions to include in the tip block
107+
pub tip_transactions: Vec<StacksTransaction>,
108+
/// Do not fail if a transaction returns error (by default the BootPlan will stop on tx failure)
109+
pub ignore_transaction_errors: bool,
106110
}
107111

108112
impl NakamotoBootPlan {
@@ -123,6 +127,8 @@ impl NakamotoBootPlan {
123127
network_id: default_config.network_id,
124128
txindex: false,
125129
epochs: None,
130+
tip_transactions: vec![],
131+
ignore_transaction_errors: false,
126132
}
127133
}
128134

@@ -222,6 +228,16 @@ impl NakamotoBootPlan {
222228
self
223229
}
224230

231+
pub fn with_tip_transactions(mut self, tip_transactions: Vec<StacksTransaction>) -> Self {
232+
self.tip_transactions = tip_transactions;
233+
self
234+
}
235+
236+
pub fn with_ignore_transaction_errors(mut self, ignore_transaction_errors: bool) -> Self {
237+
self.ignore_transaction_errors = ignore_transaction_errors;
238+
self
239+
}
240+
225241
pub fn with_test_stackers(mut self, test_stackers: Vec<TestStacker>) -> Self {
226242
self.test_stackers = test_stackers;
227243
self
@@ -476,6 +492,7 @@ impl NakamotoBootPlan {
476492
let test_signers = self.test_signers.clone();
477493
let pox_constants = self.pox_constants.clone();
478494
let test_stackers = self.test_stackers.clone();
495+
let ignore_transaction_errors = self.ignore_transaction_errors;
479496

480497
let (mut peer, mut other_peers) = self.boot_nakamoto_peers(observer);
481498
if boot_plan.is_empty() {
@@ -824,11 +841,13 @@ impl NakamotoBootPlan {
824841
// transactions processed in the same order
825842
assert_eq!(receipt.transaction.txid(), tx.txid());
826843
// no CheckErrors
827-
assert!(
828-
receipt.vm_error.is_none(),
829-
"Receipt had a CheckErrors: {:?}",
830-
&receipt
831-
);
844+
if !ignore_transaction_errors {
845+
assert!(
846+
receipt.vm_error.is_none(),
847+
"Receipt had a CheckErrors: {:?}",
848+
&receipt
849+
);
850+
}
832851
// transaction was not aborted post-hoc
833852
assert!(!receipt.post_condition_aborted);
834853
}

0 commit comments

Comments
 (0)