|
17 | 17 | use std::{error, fmt, fs, io};
|
18 | 18 |
|
19 | 19 | use clarity::vm::types::PrincipalData;
|
20 |
| -use serde::Deserialize; |
| 20 | +use serde::de::Error as DeError; |
| 21 | +use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
21 | 22 | use serde_json::json;
|
22 | 23 | use stacks_common::types::chainstate::{
|
23 | 24 | BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, TrieHash, VRFSeed,
|
@@ -374,6 +375,132 @@ pub fn stacks_addr_serialize(addr: &StacksAddress) -> serde_json::Value {
|
374 | 375 | })
|
375 | 376 | }
|
376 | 377 |
|
| 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 | + |
| 404 | +/// Serialization function for serializing extended information within the BlockstackOperationType |
| 405 | +/// that is not printed via the standard serde implementation. Specifically, serializes additional |
| 406 | +/// StacksAddress information. |
| 407 | +pub fn blockstack_op_extended_serialize_opt<S: Serializer>( |
| 408 | + op: &Option<BlockstackOperationType>, |
| 409 | + s: S, |
| 410 | +) -> Result<S::Ok, S::Error> { |
| 411 | + match op { |
| 412 | + Some(op) => { |
| 413 | + let value = op.blockstack_op_to_json(); |
| 414 | + value.serialize(s) |
| 415 | + } |
| 416 | + None => s.serialize_none(), |
| 417 | + } |
| 418 | +} |
| 419 | + |
| 420 | +/// Deserialize the burnchain op that was serialized with blockstack_op_to_json |
| 421 | +pub fn blockstack_op_extended_deserialize<'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 | +fn normalize_common_fields<'de, D>( |
| 480 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 481 | +) -> Result<(), D::Error> |
| 482 | +where |
| 483 | + D: Deserializer<'de>, |
| 484 | +{ |
| 485 | + if let Some(hex_str) = map |
| 486 | + .get("burn_header_hash") |
| 487 | + .and_then(serde_json::Value::as_str) |
| 488 | + { |
| 489 | + let cleaned = hex_str.strip_prefix("0x").unwrap_or(hex_str); |
| 490 | + let val = BurnchainHeaderHash::from_hex(cleaned).map_err(DeError::custom)?; |
| 491 | + let ser_val = serde_json::to_value(val).map_err(DeError::custom)?; |
| 492 | + map.insert("burn_header_hash".to_string(), ser_val); |
| 493 | + } |
| 494 | + |
| 495 | + if let Some(val) = map.remove("burn_txid") { |
| 496 | + map.insert("txid".to_string(), val); |
| 497 | + } |
| 498 | + if let Some(val) = map.remove("burn_block_height") { |
| 499 | + map.insert("block_height".to_string(), val); |
| 500 | + } |
| 501 | + Ok(()) |
| 502 | +} |
| 503 | + |
377 | 504 | impl BlockstackOperationType {
|
378 | 505 | pub fn opcode(&self) -> Opcodes {
|
379 | 506 | match *self {
|
@@ -475,6 +602,114 @@ impl BlockstackOperationType {
|
475 | 602 | };
|
476 | 603 | }
|
477 | 604 |
|
| 605 | + // Replace all the normalize_* functions with minimal implementations |
| 606 | + fn normalize_pre_stx_fields<'de, D>( |
| 607 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 608 | + ) -> Result<(), D::Error> |
| 609 | + where |
| 610 | + D: Deserializer<'de>, |
| 611 | + { |
| 612 | + normalize_common_fields::<D>(map)?; |
| 613 | + if let Some(serde_json::Value::Object(obj)) = map.get_mut("output") { |
| 614 | + normalize_stacks_addr_fields::<D>(obj)?; |
| 615 | + } |
| 616 | + Ok(()) |
| 617 | + } |
| 618 | + |
| 619 | + fn normalize_stack_stx_fields<'de, D>( |
| 620 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 621 | + ) -> Result<(), D::Error> |
| 622 | + where |
| 623 | + D: Deserializer<'de>, |
| 624 | + { |
| 625 | + normalize_common_fields::<D>(map)?; |
| 626 | + if let Some(serde_json::Value::Object(obj)) = map.get_mut("sender") { |
| 627 | + normalize_stacks_addr_fields::<D>(obj)?; |
| 628 | + } |
| 629 | + if let Some(reward_val) = map.get("reward_addr") { |
| 630 | + let b58_str = reward_val |
| 631 | + .as_str() |
| 632 | + .ok_or_else(|| DeError::custom("Expected base58 string in reward_addr"))?; |
| 633 | + let addr = PoxAddress::from_b58(b58_str) |
| 634 | + .ok_or_else(|| DeError::custom("Invalid stacks address"))?; |
| 635 | + let val = serde_json::to_value(addr).map_err(DeError::custom)?; |
| 636 | + map.insert("reward_addr".into(), val); |
| 637 | + } |
| 638 | + Ok(()) |
| 639 | + } |
| 640 | + |
| 641 | + fn normalize_transfer_stx_fields<'de, D>( |
| 642 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 643 | + ) -> Result<(), D::Error> |
| 644 | + where |
| 645 | + D: Deserializer<'de>, |
| 646 | + { |
| 647 | + normalize_common_fields::<D>(map)?; |
| 648 | + for field in ["recipient", "sender"] { |
| 649 | + if let Some(serde_json::Value::Object(obj)) = map.get_mut(field) { |
| 650 | + normalize_stacks_addr_fields::<D>(obj)?; |
| 651 | + } |
| 652 | + } |
| 653 | + if let Some(memo_str) = map.get("memo").and_then(serde_json::Value::as_str) { |
| 654 | + let memo_hex = memo_str.trim_start_matches("0x"); |
| 655 | + let memo_bytes = hex_bytes(memo_hex).map_err(DeError::custom)?; |
| 656 | + let val = serde_json::to_value(memo_bytes).map_err(DeError::custom)?; |
| 657 | + map.insert("memo".into(), val); |
| 658 | + } |
| 659 | + Ok(()) |
| 660 | + } |
| 661 | + |
| 662 | + fn normalize_delegate_stx_fields<'de, D>( |
| 663 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 664 | + ) -> Result<(), D::Error> |
| 665 | + where |
| 666 | + D: Deserializer<'de>, |
| 667 | + { |
| 668 | + normalize_common_fields::<D>(map)?; |
| 669 | + if let Some(serde_json::Value::Array(arr)) = map.get("reward_addr") { |
| 670 | + if arr.len() == 2 { |
| 671 | + let index = arr[0] |
| 672 | + .as_u64() |
| 673 | + .ok_or_else(|| DeError::custom("Expected u64 index"))? |
| 674 | + as u32; |
| 675 | + let b58_str = arr[1] |
| 676 | + .as_str() |
| 677 | + .ok_or_else(|| DeError::custom("Expected base58 string"))?; |
| 678 | + let addr = PoxAddress::from_b58(b58_str) |
| 679 | + .ok_or_else(|| DeError::custom("Invalid stacks address"))?; |
| 680 | + let val = serde_json::to_value((index, addr)).map_err(DeError::custom)?; |
| 681 | + map.insert("reward_addr".into(), val); |
| 682 | + } |
| 683 | + } |
| 684 | + for field in ["delegate_to", "sender"] { |
| 685 | + if let Some(serde_json::Value::Object(obj)) = map.get_mut(field) { |
| 686 | + normalize_stacks_addr_fields::<D>(obj)?; |
| 687 | + } |
| 688 | + } |
| 689 | + Ok(()) |
| 690 | + } |
| 691 | + |
| 692 | + fn normalize_vote_for_aggregate_key_fields<'de, D>( |
| 693 | + map: &mut serde_json::Map<String, serde_json::Value>, |
| 694 | + ) -> Result<(), D::Error> |
| 695 | + where |
| 696 | + D: Deserializer<'de>, |
| 697 | + { |
| 698 | + normalize_common_fields::<D>(map)?; |
| 699 | + for field in ["aggregate_key", "signer_key"] { |
| 700 | + if let Some(hex_str) = map.get(field).and_then(serde_json::Value::as_str) { |
| 701 | + let cleaned = hex_str.strip_prefix("0x").unwrap_or(hex_str); |
| 702 | + let val = StacksPublicKeyBuffer::from_hex(cleaned).map_err(DeError::custom)?; |
| 703 | + let ser_val = serde_json::to_value(val).map_err(DeError::custom)?; |
| 704 | + map.insert(field.to_string(), ser_val); |
| 705 | + } |
| 706 | + } |
| 707 | + if let Some(serde_json::Value::Object(obj)) = map.get_mut("sender") { |
| 708 | + normalize_stacks_addr_fields::<D>(obj)?; |
| 709 | + } |
| 710 | + Ok(()) |
| 711 | + } |
| 712 | + |
478 | 713 | pub fn pre_stx_to_json(op: &PreStxOp) -> serde_json::Value {
|
479 | 714 | json!({
|
480 | 715 | "pre_stx": {
|
|
0 commit comments