Skip to content

Commit 1e55e15

Browse files
committed
Create TransactionEventPayload struct and add vm_error to it
Signed-off-by: Jacinta Ferrant <jacinta.ferrant@gmail.com>
1 parent 31d048c commit 1e55e15

File tree

3 files changed

+121
-74
lines changed

3 files changed

+121
-74
lines changed

stackslib/src/chainstate/burn/operations/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use std::{error, fmt, fs, io};
1818

1919
use clarity::vm::types::PrincipalData;
20-
use serde::Deserialize;
20+
use serde::{Deserialize, Serialize, Serializer};
2121
use serde_json::json;
2222
use stacks_common::types::chainstate::{
2323
BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, TrieHash, VRFSeed,
@@ -374,6 +374,22 @@ pub fn stacks_addr_serialize(addr: &StacksAddress) -> serde_json::Value {
374374
})
375375
}
376376

377+
/// Serialization function for serializing extended information within the BlockstackOperationType
378+
/// that is not printed via the standard serde implenentation. Specifically serializes additional
379+
/// StacksAddress information that is normally lost.
380+
pub fn blockstack_op_extended_serialize_opt<S: Serializer>(
381+
op: &Option<BlockstackOperationType>,
382+
s: S,
383+
) -> Result<S::Ok, S::Error> {
384+
match op {
385+
Some(op) => {
386+
let value = op.blockstack_op_to_json();
387+
value.serialize(s)
388+
}
389+
None => s.serialize_none(),
390+
}
391+
}
392+
377393
impl BlockstackOperationType {
378394
pub fn opcode(&self) -> Opcodes {
379395
match *self {

stackslib/src/net/api/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,37 @@ pub mod prefix_hex {
225225
}
226226
}
227227

228+
/// This module serde encode and decodes structs that
229+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
230+
pub mod prefix_hex_codec {
231+
use clarity::codec::StacksMessageCodec;
232+
use clarity::util::hash::{hex_bytes, to_hex};
233+
234+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
235+
val: &T,
236+
s: S,
237+
) -> Result<S::Ok, S::Error> {
238+
let mut bytes = vec![];
239+
val.consensus_serialize(&mut bytes)
240+
.map_err(serde::ser::Error::custom)?;
241+
s.serialize_str(&format!("0x{}", to_hex(&bytes)))
242+
}
243+
244+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
245+
d: D,
246+
) -> Result<T, D::Error> {
247+
let inst_str: String = serde::Deserialize::deserialize(d)?;
248+
let Some(hex_str) = inst_str.get(2..) else {
249+
return Err(serde::de::Error::invalid_length(
250+
inst_str.len(),
251+
&"at least length 2 string",
252+
));
253+
};
254+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
255+
T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)
256+
}
257+
}
258+
228259
pub trait HexDeser: Sized {
229260
fn try_from(hex: &str) -> Result<Self, HexError>;
230261
}
@@ -247,3 +278,4 @@ impl_hex_deser!(ConsensusHash);
247278
impl_hex_deser!(BlockHeaderHash);
248279
impl_hex_deser!(Hash160);
249280
impl_hex_deser!(Sha512Trunc256Sum);
281+
impl_hex_deser!(Txid);

testnet/stacks-node/src/event_dispatcher.rs

Lines changed: 72 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use std::sync::{Arc, Mutex};
2424
use std::thread::sleep;
2525
use std::time::Duration;
2626

27-
use clarity::vm::analysis::contract_interface_builder::build_contract_interface;
27+
use clarity::vm::analysis::contract_interface_builder::{
28+
build_contract_interface, ContractInterface,
29+
};
2830
use clarity::vm::costs::ExecutionCost;
2931
use clarity::vm::events::{FTEventType, NFTEventType, STXEventType};
3032
use clarity::vm::types::{AssetIdentifier, QualifiedContractIdentifier, Value};
@@ -34,7 +36,9 @@ use rand::Rng;
3436
use rusqlite::{params, Connection};
3537
use serde_json::json;
3638
use stacks::burnchains::{PoxConstants, Txid};
37-
use stacks::chainstate::burn::operations::BlockstackOperationType;
39+
use stacks::chainstate::burn::operations::{
40+
blockstack_op_extended_serialize_opt, BlockstackOperationType,
41+
};
3842
use stacks::chainstate::burn::ConsensusHash;
3943
use stacks::chainstate::coordinator::BlockEventDispatcher;
4044
use stacks::chainstate::nakamoto::NakamotoBlock;
@@ -59,6 +63,7 @@ use stacks::libstackerdb::StackerDBChunkData;
5963
use stacks::net::api::postblock_proposal::{
6064
BlockValidateOk, BlockValidateReject, BlockValidateResponse,
6165
};
66+
use stacks::net::api::{prefix_hex, prefix_hex_codec, prefix_opt_hex};
6267
use stacks::net::atlas::{Attachment, AttachmentInstance};
6368
use stacks::net::http::HttpRequestContents;
6469
use stacks::net::httpcore::{send_http_request, StacksHttpRequest};
@@ -95,15 +100,6 @@ pub struct EventObserver {
95100
pub disable_retries: bool,
96101
}
97102

98-
struct ReceiptPayloadInfo<'a> {
99-
txid: String,
100-
success: &'a str,
101-
raw_result: String,
102-
raw_tx: String,
103-
contract_interface_json: serde_json::Value,
104-
burnchain_op_json: serde_json::Value,
105-
}
106-
107103
const STATUS_RESP_TRUE: &str = "success";
108104
const STATUS_RESP_NOT_COMMITTED: &str = "abort_by_response";
109105
const STATUS_RESP_POST_CONDITION: &str = "abort_by_post_condition";
@@ -334,6 +330,39 @@ impl RewardSetEventPayload {
334330
}
335331
}
336332

333+
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
334+
pub struct TransactionEventPayload<'a> {
335+
#[serde(with = "prefix_hex")]
336+
/// The transaction id
337+
pub txid: Txid,
338+
/// The transaction index
339+
pub tx_index: u32,
340+
/// The transaction status
341+
pub status: &'a str,
342+
#[serde(with = "prefix_hex_codec")]
343+
/// The raw transaction result
344+
pub raw_result: Value,
345+
/// The hex encoded raw transaction
346+
pub raw_tx: String,
347+
/// The contract interface
348+
pub contract_interface: Option<ContractInterface>,
349+
/// The burnchain op
350+
#[serde(serialize_with = "blockstack_op_extended_serialize_opt")]
351+
pub burnchain_op: Option<BlockstackOperationType>,
352+
/// The transaction execution cost
353+
pub execution_cost: ExecutionCost,
354+
/// The microblock sequence
355+
pub microblock_sequence: Option<u16>,
356+
#[serde(with = "prefix_opt_hex")]
357+
/// The microblock hash
358+
pub microblock_hash: Option<BlockHeaderHash>,
359+
#[serde(with = "prefix_opt_hex")]
360+
/// The microblock parent hash
361+
pub microblock_parent_hash: Option<BlockHeaderHash>,
362+
/// Error information if one occurred in the Clarity VM
363+
pub vm_error: Option<String>,
364+
}
365+
337366
#[cfg(test)]
338367
static TEST_EVENT_OBSERVER_SKIP_RETRY: LazyLock<TestFlag<bool>> = LazyLock::new(TestFlag::default);
339368

@@ -573,11 +602,14 @@ impl EventObserver {
573602
})
574603
}
575604

576-
/// Returns tuple of (txid, success, raw_result, raw_tx, contract_interface_json)
577-
fn generate_payload_info_for_receipt(receipt: &StacksTransactionReceipt) -> ReceiptPayloadInfo {
605+
/// Returns transaction event payload to send for new block or microblock event
606+
fn make_new_block_txs_payload(
607+
receipt: &StacksTransactionReceipt,
608+
tx_index: u32,
609+
) -> TransactionEventPayload {
578610
let tx = &receipt.transaction;
579611

580-
let success = match (receipt.post_condition_aborted, &receipt.result) {
612+
let status = match (receipt.post_condition_aborted, &receipt.result) {
581613
(false, Value::Response(response_data)) => {
582614
if response_data.committed {
583615
STATUS_RESP_TRUE
@@ -587,77 +619,44 @@ impl EventObserver {
587619
}
588620
(true, Value::Response(_)) => STATUS_RESP_POST_CONDITION,
589621
_ => {
590-
if let TransactionOrigin::Stacks(inner_tx) = &tx {
591-
if let TransactionPayload::PoisonMicroblock(..) = &inner_tx.payload {
592-
STATUS_RESP_TRUE
593-
} else {
594-
unreachable!() // Transaction results should otherwise always be a Value::Response type
595-
}
596-
} else {
597-
unreachable!() // Transaction results should always be a Value::Response type
622+
let TransactionOrigin::Stacks(inner_tx) = tx else {
623+
unreachable!("Transaction results should always be a Value::Response type");
624+
};
625+
if !matches!(inner_tx.payload, TransactionPayload::PoisonMicroblock(..)) {
626+
unreachable!("Transaction results should always be a Value::Response type");
598627
}
628+
STATUS_RESP_TRUE
599629
}
600630
};
601631

602-
let (txid, raw_tx, burnchain_op_json) = match tx {
603-
TransactionOrigin::Burn(op) => (
604-
op.txid().to_string(),
605-
"00".to_string(),
606-
BlockstackOperationType::blockstack_op_to_json(op),
607-
),
632+
let (txid, raw_tx, burnchain_op) = match tx {
633+
TransactionOrigin::Burn(op) => (op.txid(), "0x00".to_string(), Some(op.clone())),
608634
TransactionOrigin::Stacks(ref tx) => {
609-
let txid = tx.txid().to_string();
610-
let bytes = tx.serialize_to_vec();
611-
(txid, bytes_to_hex(&bytes), json!(null))
635+
let txid = tx.txid();
636+
let bytes = bytes_to_hex(&tx.serialize_to_vec());
637+
(txid, format!("0x{bytes}"), None)
612638
}
613639
};
614640

615-
let raw_result = {
616-
let bytes = receipt
617-
.result
618-
.serialize_to_vec()
619-
.expect("FATAL: failed to serialize transaction receipt");
620-
bytes_to_hex(&bytes)
621-
};
622-
let contract_interface_json = {
623-
match &receipt.contract_analysis {
624-
Some(analysis) => json!(build_contract_interface(analysis)
625-
.expect("FATAL: failed to serialize contract publish receipt")),
626-
None => json!(null),
627-
}
628-
};
629-
ReceiptPayloadInfo {
641+
TransactionEventPayload {
630642
txid,
631-
success,
632-
raw_result,
643+
tx_index,
644+
status,
645+
raw_result: receipt.result.clone(),
633646
raw_tx,
634-
contract_interface_json,
635-
burnchain_op_json,
647+
contract_interface: receipt.contract_analysis.as_ref().map(|analysis| {
648+
build_contract_interface(analysis)
649+
.expect("FATAL: failed to serialize contract publish receipt")
650+
}),
651+
burnchain_op,
652+
execution_cost: receipt.execution_cost.clone(),
653+
microblock_sequence: receipt.microblock_header.as_ref().map(|x| x.sequence),
654+
microblock_hash: receipt.microblock_header.as_ref().map(|x| x.block_hash()),
655+
microblock_parent_hash: receipt.microblock_header.as_ref().map(|x| x.prev_block),
656+
vm_error: receipt.vm_error.clone(),
636657
}
637658
}
638659

639-
/// Returns json payload to send for new block or microblock event
640-
fn make_new_block_txs_payload(
641-
receipt: &StacksTransactionReceipt,
642-
tx_index: u32,
643-
) -> serde_json::Value {
644-
let receipt_payload_info = EventObserver::generate_payload_info_for_receipt(receipt);
645-
646-
json!({
647-
"txid": format!("0x{}", &receipt_payload_info.txid),
648-
"tx_index": tx_index,
649-
"status": receipt_payload_info.success,
650-
"raw_result": format!("0x{}", &receipt_payload_info.raw_result),
651-
"raw_tx": format!("0x{}", &receipt_payload_info.raw_tx),
652-
"contract_abi": receipt_payload_info.contract_interface_json,
653-
"burnchain_op": receipt_payload_info.burnchain_op_json,
654-
"execution_cost": receipt.execution_cost,
655-
"microblock_sequence": receipt.microblock_header.as_ref().map(|x| x.sequence),
656-
"microblock_hash": receipt.microblock_header.as_ref().map(|x| format!("0x{}", x.block_hash())),
657-
"microblock_parent_hash": receipt.microblock_header.as_ref().map(|x| format!("0x{}", x.prev_block)),
658-
})
659-
}
660-
661660
fn make_new_attachment_payload(
662661
attachment: &(AttachmentInstance, Attachment),
663662
) -> serde_json::Value {
@@ -686,7 +685,7 @@ impl EventObserver {
686685
&self,
687686
parent_index_block_hash: StacksBlockId,
688687
filtered_events: Vec<(usize, &(bool, Txid, &StacksTransactionEvent))>,
689-
serialized_txs: &Vec<serde_json::Value>,
688+
serialized_txs: &Vec<TransactionEventPayload>,
690689
burn_block_hash: BurnchainHeaderHash,
691690
burn_block_height: u32,
692691
burn_block_timestamp: u64,

0 commit comments

Comments
 (0)