Skip to content

Commit cac00cc

Browse files
committed
Add deserializer for custom blockstack op fields and make sure backwards compatibility satisfied for TransactionEventPayload
Signed-off-by: Jacinta Ferrant <jacinta.ferrant@gmail.com>
1 parent 5979c9d commit cac00cc

File tree

3 files changed

+553
-23
lines changed

3 files changed

+553
-23
lines changed

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

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

1919
use clarity::vm::types::PrincipalData;
20-
use serde::{Deserialize, Serialize, Serializer};
20+
use serde::de::Error as DeError;
21+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
2122
use serde_json::json;
2223
use stacks_common::types::chainstate::{
2324
BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, TrieHash, VRFSeed,
@@ -374,6 +375,32 @@ pub fn stacks_addr_serialize(addr: &StacksAddress) -> serde_json::Value {
374375
})
375376
}
376377

378+
fn normalize_stacks_addr_fields<'de, D>(
379+
inner: &mut serde_json::Map<String, serde_json::Value>,
380+
) -> Result<(), D::Error>
381+
where
382+
D: Deserializer<'de>,
383+
{
384+
// Rename `address_version` to `version`
385+
if let Some(address_version) = inner.remove("address_version") {
386+
inner.insert("version".to_string(), address_version);
387+
}
388+
389+
// Rename `address_hash_bytes` to `bytes` and convert to bytes
390+
if let Some(address_bytes) = inner
391+
.remove("address_hash_bytes")
392+
.and_then(|addr| serde_json::Value::as_str(&addr).map(|x| x.to_string()))
393+
{
394+
let address_hex: String = address_bytes.chars().skip(2).collect(); // Remove "0x" prefix
395+
inner.insert(
396+
"bytes".to_string(),
397+
serde_json::to_value(&address_hex).map_err(DeError::custom)?,
398+
);
399+
}
400+
401+
Ok(())
402+
}
403+
377404
/// Serialization function for serializing extended information within the BlockstackOperationType
378405
/// that is not printed via the standard serde implementation. Specifically, serializes additional
379406
/// StacksAddress information.
@@ -390,6 +417,101 @@ pub fn blockstack_op_extended_serialize_opt<S: Serializer>(
390417
}
391418
}
392419

420+
/// Deserialize the burnchain op that was serialized with blockstack_op_to_json
421+
pub fn deserialize_extended_blockstack_op<'de, D>(
422+
deserializer: D,
423+
) -> Result<Option<BlockstackOperationType>, D::Error>
424+
where
425+
D: Deserializer<'de>,
426+
{
427+
use serde::de::Error as DeError;
428+
use serde_json::{Map, Value};
429+
430+
let raw: Option<Value> = Option::deserialize(deserializer)?;
431+
let Some(Value::Object(mut obj)) = raw else {
432+
return Ok(None);
433+
};
434+
435+
let Some((key, value)) = obj.iter_mut().next() else {
436+
return Ok(None);
437+
};
438+
439+
let inner = value
440+
.as_object_mut()
441+
.ok_or_else(|| DeError::custom("Expected blockstack op to be an object"))?;
442+
443+
let normalized_key = match key.as_str() {
444+
"pre_stx" => {
445+
BlockstackOperationType::normalize_pre_stx_fields::<D>(inner)?;
446+
"PreStx"
447+
}
448+
"stack_stx" => {
449+
BlockstackOperationType::normalize_stack_stx_fields::<D>(inner)?;
450+
"StackStx"
451+
}
452+
"transfer_stx" => {
453+
BlockstackOperationType::normalize_transfer_stx_fields::<D>(inner)?;
454+
"TransferStx"
455+
}
456+
"delegate_stx" => {
457+
BlockstackOperationType::normalize_delegate_stx_fields::<D>(inner)?;
458+
"DelegateStx"
459+
}
460+
"vote_for_aggregate_key" => {
461+
BlockstackOperationType::normalize_vote_for_aggregate_key_fields::<D>(inner)?;
462+
"VoteForAggregateKey"
463+
}
464+
"leader_key_register" => "LeaderKeyRegister",
465+
"leader_block_commit" => "LeaderBlockCommit",
466+
other => other,
467+
};
468+
469+
let mut map = Map::new();
470+
map.insert(normalized_key.to_string(), value.clone());
471+
472+
let normalized = Value::Object(map);
473+
474+
serde_json::from_value(normalized)
475+
.map(Some)
476+
.map_err(serde::de::Error::custom)
477+
}
478+
479+
macro_rules! normalize_common_fields {
480+
($map:ident, $de:ident) => {{
481+
normalize_hex_field::<$de, _>(&mut $map, "burn_header_hash", |s| {
482+
BurnchainHeaderHash::from_hex(s).map_err(DeError::custom)
483+
})?;
484+
rename_field(&mut $map, "burn_txid", "txid");
485+
rename_field(&mut $map, "burn_block_height", "block_height");
486+
}};
487+
}
488+
489+
// Utility function to normalize a hex string to a BurnchainHeaderHash JSON value
490+
fn normalize_hex_field<'de, D, T>(
491+
map: &mut serde_json::Map<String, serde_json::Value>,
492+
field: &str,
493+
from_hex: fn(&str) -> Result<T, D::Error>,
494+
) -> Result<(), D::Error>
495+
where
496+
D: Deserializer<'de>,
497+
T: serde::Serialize,
498+
{
499+
if let Some(hex_str) = map.get(field).and_then(serde_json::Value::as_str) {
500+
let cleaned = hex_str.strip_prefix("0x").unwrap_or(hex_str);
501+
let val = from_hex(cleaned).map_err(DeError::custom)?;
502+
let ser_val = serde_json::to_value(val).map_err(DeError::custom)?;
503+
map.insert(field.to_string(), ser_val);
504+
}
505+
Ok(())
506+
}
507+
508+
// Normalize renamed field
509+
fn rename_field(map: &mut serde_json::Map<String, serde_json::Value>, from: &str, to: &str) {
510+
if let Some(val) = map.remove(from) {
511+
map.insert(to.to_string(), val);
512+
}
513+
}
514+
393515
impl BlockstackOperationType {
394516
pub fn opcode(&self) -> Opcodes {
395517
match *self {
@@ -491,6 +613,114 @@ impl BlockstackOperationType {
491613
};
492614
}
493615

616+
// Replace all the normalize_* functions with minimal implementations
617+
fn normalize_pre_stx_fields<'de, D>(
618+
mut map: &mut serde_json::Map<String, serde_json::Value>,
619+
) -> Result<(), D::Error>
620+
where
621+
D: Deserializer<'de>,
622+
{
623+
normalize_common_fields!(map, D);
624+
if let Some(serde_json::Value::Object(obj)) = map.get_mut("output") {
625+
normalize_stacks_addr_fields::<D>(obj)?;
626+
}
627+
Ok(())
628+
}
629+
630+
fn normalize_stack_stx_fields<'de, D>(
631+
mut map: &mut serde_json::Map<String, serde_json::Value>,
632+
) -> Result<(), D::Error>
633+
where
634+
D: Deserializer<'de>,
635+
{
636+
normalize_common_fields!(map, D);
637+
if let Some(serde_json::Value::Object(obj)) = map.get_mut("sender") {
638+
normalize_stacks_addr_fields::<D>(obj)?;
639+
}
640+
if let Some(reward_val) = map.get("reward_addr") {
641+
let b58_str = reward_val
642+
.as_str()
643+
.ok_or_else(|| DeError::custom("Expected base58 string in reward_addr"))?;
644+
let addr = PoxAddress::from_b58(b58_str)
645+
.ok_or_else(|| DeError::custom("Invalid stacks address"))?;
646+
let val = serde_json::to_value(addr).map_err(DeError::custom)?;
647+
map.insert("reward_addr".into(), val);
648+
}
649+
Ok(())
650+
}
651+
652+
fn normalize_transfer_stx_fields<'de, D>(
653+
mut map: &mut serde_json::Map<String, serde_json::Value>,
654+
) -> Result<(), D::Error>
655+
where
656+
D: Deserializer<'de>,
657+
{
658+
normalize_common_fields!(map, D);
659+
for field in ["recipient", "sender"] {
660+
if let Some(serde_json::Value::Object(obj)) = map.get_mut(field) {
661+
normalize_stacks_addr_fields::<D>(obj)?;
662+
}
663+
}
664+
if let Some(memo_str) = map.get("memo").and_then(serde_json::Value::as_str) {
665+
let memo_hex = memo_str.trim_start_matches("0x");
666+
let memo_bytes = hex_bytes(memo_hex).map_err(DeError::custom)?;
667+
let val = serde_json::to_value(memo_bytes).map_err(DeError::custom)?;
668+
map.insert("memo".into(), val);
669+
}
670+
Ok(())
671+
}
672+
673+
fn normalize_delegate_stx_fields<'de, D>(
674+
mut map: &mut serde_json::Map<String, serde_json::Value>,
675+
) -> Result<(), D::Error>
676+
where
677+
D: Deserializer<'de>,
678+
{
679+
normalize_common_fields!(map, D);
680+
if let Some(serde_json::Value::Array(arr)) = map.get("reward_addr") {
681+
if arr.len() == 2 {
682+
let index = arr[0]
683+
.as_u64()
684+
.ok_or_else(|| DeError::custom("Expected u64 index"))?
685+
as u32;
686+
let b58_str = arr[1]
687+
.as_str()
688+
.ok_or_else(|| DeError::custom("Expected base58 string"))?;
689+
let addr = PoxAddress::from_b58(b58_str)
690+
.ok_or_else(|| DeError::custom("Invalid stacks address"))?;
691+
let val = serde_json::to_value((index, addr)).map_err(DeError::custom)?;
692+
map.insert("reward_addr".into(), val);
693+
}
694+
}
695+
for field in ["delegate_to", "sender"] {
696+
if let Some(serde_json::Value::Object(obj)) = map.get_mut(field) {
697+
normalize_stacks_addr_fields::<D>(obj)?;
698+
}
699+
}
700+
Ok(())
701+
}
702+
703+
fn normalize_vote_for_aggregate_key_fields<'de, D>(
704+
mut map: &mut serde_json::Map<String, serde_json::Value>,
705+
) -> Result<(), D::Error>
706+
where
707+
D: Deserializer<'de>,
708+
{
709+
normalize_common_fields!(map, D);
710+
for field in ["aggregate_key", "signer_key"] {
711+
if let Some(hex_str) = map.get(field).and_then(serde_json::Value::as_str) {
712+
let cleaned = hex_str.strip_prefix("0x").unwrap_or(hex_str);
713+
let val = StacksPublicKeyBuffer::from_hex(cleaned).map_err(DeError::custom)?;
714+
let ser_val = serde_json::to_value(val).map_err(DeError::custom)?;
715+
map.insert(field.to_string(), ser_val);
716+
}
717+
}
718+
if let Some(serde_json::Value::Object(obj)) = map.get_mut("sender") {
719+
normalize_stacks_addr_fields::<D>(obj)?;
720+
}
721+
Ok(())
722+
}
723+
494724
pub fn pre_stx_to_json(op: &PreStxOp) -> serde_json::Value {
495725
json!({
496726
"pre_stx": {

0 commit comments

Comments
 (0)