Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/rpc/components/schemas/block-replay.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,7 @@ properties:
description: index of the transaction in the array of transactions
txid:
type: string
description: transaction id
description: transaction id
vm_error:
type: string
description: optional vm error (for runtime failures)
3 changes: 3 additions & 0 deletions stackslib/src/net/api/blockreplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ pub struct RPCReplayedBlockTransaction {
pub execution_cost: ExecutionCost,
/// generated events
pub events: Vec<serde_json::Value>,
/// optional vm error
pub vm_error: Option<String>,
}

impl RPCReplayedBlockTransaction {
Expand Down Expand Up @@ -249,6 +251,7 @@ impl RPCReplayedBlockTransaction {
stx_burned: receipt.stx_burned,
execution_cost: receipt.execution_cost.clone(),
events,
vm_error: receipt.vm_error.clone(),
}
}
}
Expand Down
93 changes: 92 additions & 1 deletion stackslib/src/net/api/tests/blockreplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@

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

use stacks_common::consts::CHAIN_ID_TESTNET;
use stacks_common::types::chainstate::StacksBlockId;

use crate::chainstate::stacks::Error as ChainError;
use crate::chainstate::stacks::{Error as ChainError, StacksTransaction};
use crate::core::test_util::make_contract_publish;
use crate::net::api::blockreplay;
use crate::net::api::tests::TestRPC;
use crate::net::connection::ConnectionOptions;
use crate::net::httpcore::{StacksHttp, StacksHttpRequest};
use crate::net::test::TestEventObserver;
use crate::net::ProtocolFamily;
use crate::stacks_common::codec::StacksMessageCodec;

#[test]
fn test_try_parse_request() {
Expand Down Expand Up @@ -179,3 +182,91 @@ fn test_try_make_response() {
let (preamble, body) = response.destruct();
assert_eq!(preamble.status_code, 401);
}

#[test]
fn test_try_make_response_with_unsuccessful_transaction() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);

let test_observer = TestEventObserver::new();
let rpc_test =
TestRPC::setup_nakamoto_with_boot_plan(function_name!(), &test_observer, |boot_plan| {
let mut tip_transactions: Vec<StacksTransaction> = vec![];

let miner_privk = boot_plan.private_key.clone();

let contract_code = "(broken)";

let deploy_tx_bytes = make_contract_publish(
&miner_privk,
100,
1000,
CHAIN_ID_TESTNET,
&"err-contract",
&contract_code,
);
let deploy_tx =
StacksTransaction::consensus_deserialize(&mut deploy_tx_bytes.as_slice()).unwrap();

tip_transactions.push(deploy_tx);
boot_plan
.with_tip_transactions(tip_transactions)
.with_ignore_transaction_errors(true)
});

let tip_block = test_observer.get_blocks().last().unwrap().clone();

let nakamoto_consensus_hash = rpc_test.consensus_hash.clone();

let mut requests = vec![];

let mut request =
StacksHttpRequest::new_block_replay(addr.clone().into(), &rpc_test.canonical_tip);
// add the authorization header
request.add_header("authorization".into(), "password".into());
requests.push(request);

let mut responses = rpc_test.run(requests);

// got the Nakamoto tip
let response = responses.remove(0);

debug!(
"Response:\n{}\n",
std::str::from_utf8(&response.try_serialize().unwrap()).unwrap()
);

let resp = response.decode_replayed_block().unwrap();

assert_eq!(resp.consensus_hash, nakamoto_consensus_hash);
assert_eq!(resp.consensus_hash, tip_block.metadata.consensus_hash);

assert_eq!(resp.block_hash, tip_block.block.block_hash);
assert_eq!(resp.block_id, tip_block.metadata.index_block_hash());
assert_eq!(resp.parent_block_id, tip_block.parent);

assert_eq!(resp.block_height, tip_block.metadata.stacks_block_height);

assert!(resp.valid_merkle_root);

assert_eq!(resp.transactions.len(), tip_block.receipts.len());

for tx_index in 0..resp.transactions.len() {
assert_eq!(
resp.transactions[tx_index].txid,
tip_block.receipts[tx_index].transaction.txid()
);
assert_eq!(
resp.transactions[tx_index].events.len(),
tip_block.receipts[tx_index].events.len()
);
assert_eq!(
resp.transactions[tx_index].result,
tip_block.receipts[tx_index].result
);
}

assert_eq!(
resp.transactions.last().unwrap().vm_error.clone().unwrap(),
":0:0: use of unresolved function 'broken'"
);
}
23 changes: 23 additions & 0 deletions stackslib/src/net/tests/inv/nakamoto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,29 @@ where

plan.initial_balances.append(&mut initial_balances);

if !plan.tip_transactions.is_empty() {
let mut tip_transactions = plan.tip_transactions.clone();
if let Some(tip_tenure) = boot_tenures.last_mut() {
match tip_tenure {
NakamotoBootTenure::Sortition(boot_steps) => match boot_steps.last_mut().unwrap() {
NakamotoBootStep::Block(transactions) => {
transactions.append(&mut tip_transactions)
}
_ => (),
},
NakamotoBootTenure::NoSortition(boot_steps) => {
let boot_steps_len = boot_steps.len();
match boot_steps.get_mut(boot_steps_len - 2).unwrap() {
NakamotoBootStep::Block(transactions) => {
transactions.append(&mut tip_transactions)
}
_ => (),
}
}
}
}
}

let (peer, other_peers) = plan.boot_into_nakamoto_peers(boot_tenures, Some(observer));
(peer, other_peers)
}
Expand Down
27 changes: 22 additions & 5 deletions stackslib/src/net/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ pub struct NakamotoBootPlan {
pub network_id: u32,
pub txindex: bool,
pub epochs: Option<EpochList<ExecutionCost>>,
pub tip_transactions: Vec<StacksTransaction>,
pub ignore_transaction_errors: bool,
}

impl NakamotoBootPlan {
Expand All @@ -124,6 +126,8 @@ impl NakamotoBootPlan {
network_id: TestPeerConfig::default().network_id,
txindex: false,
epochs: None,
tip_transactions: vec![],
ignore_transaction_errors: false,
}
}

Expand Down Expand Up @@ -169,6 +173,16 @@ impl NakamotoBootPlan {
self
}

pub fn with_tip_transactions(mut self, tip_transactions: Vec<StacksTransaction>) -> Self {
self.tip_transactions = tip_transactions;
self
}

pub fn with_ignore_transaction_errors(mut self, ignore_transaction_errors: bool) -> Self {
self.ignore_transaction_errors = ignore_transaction_errors;
self
}

pub fn with_test_stackers(mut self, test_stackers: Vec<TestStacker>) -> Self {
self.test_stackers = test_stackers;
self
Expand Down Expand Up @@ -891,6 +905,7 @@ impl NakamotoBootPlan {
let test_signers = self.test_signers.clone();
let pox_constants = self.pox_constants.clone();
let test_stackers = self.test_stackers.clone();
let ignore_transaction_errors = self.ignore_transaction_errors;

let (mut peer, mut other_peers) = self.boot_nakamoto_peers(observer);
if boot_plan.is_empty() {
Expand Down Expand Up @@ -1191,11 +1206,13 @@ impl NakamotoBootPlan {
// transactions processed in the same order
assert_eq!(receipt.transaction.txid(), tx.txid());
// no CheckErrors
assert!(
receipt.vm_error.is_none(),
"Receipt had a CheckErrors: {:?}",
&receipt
);
if !ignore_transaction_errors {
assert!(
receipt.vm_error.is_none(),
"Receipt had a CheckErrors: {:?}",
&receipt
);
}
// transaction was not aborted post-hoc
assert!(!receipt.post_condition_aborted);
}
Expand Down
Loading