added docs, serialization functions, tests, and json formatting for btc ops is readable

This commit is contained in:
Pavitthra Pandurangan
2023-02-01 16:58:50 -05:00
parent 3d2b3e56b2
commit f36473a444
4 changed files with 546 additions and 16 deletions

View File

@@ -35,6 +35,7 @@ that it is a burnchain operation. A burnchain operation is a transaction that
is executed on the Stacks network, but was sent through the Bitcoin network.
The Stacks network supports a few specific burnchain operations. You can read
more about them [here](https://github.com/stacksgov/sips/blob/main/sips/sip-007/sip-007-stacking-consensus.md#stx-operations-on-bitcoin).
The section below has example json encodings for each of the burnchain operations.
Example:
@@ -88,22 +89,22 @@ Example:
{
"burnchain_op": {
"TransferStx": {
"block_height": 208,
"burn_header_hash": [
55, 174,104,155,73,251,142,121,133,74,19,169,209,44,210,112,95,13,83,91,39,16,212,142,211,91,
234,92,56,7,85,250],
"memo": [],
"block_height": 10,
"burn_header_hash": "1410131010105914101010101013704010101010221010101010101010101010",
"memo": "0x000102030405",
"recipient": {
"bytes": "455e5d8c309ff012c55882cf7f50a4b542739b9c",
"version": 26
"address": "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K",
"address_hash_bytes": "0x89f5fd1f719e4449c980de38e3504be6770a2698",
"address_version": 22
},
"sender": {
"bytes": "9ffdc100acf4d475b71a042b76fb4447966102ab",
"version": 26
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26
},
"transfered_ustx": 100000,
"transfered_ustx": 10,
"txid": "85aa2106186723f3c4f1d8bb58e3a02746ca9be1be9f4be0c6557079e1f660e6",
"vtxindex": 2
"vtxindex": 10
}
},
"contract_abi": null,
@@ -120,7 +121,7 @@ Example:
"raw_result": "0x0703",
"raw_tx": "0x00",
"status": "success",
"tx_index": 0,
"tx_index": 2,
"txid": "0x85aa2106186723f3c4f1d8bb58e3a02746ca9be1be9f4be0c6557079e1f660e6"
}
],
@@ -151,6 +152,68 @@ Example:
}
```
#### Example json values for burnchain operations
- TransferStx
```json
{
"TransferStx": {
"block_height": 10,
"burn_header_hash": "1410131010105914101010101013704010101010221010101010101010101010",
"memo": "0x000102030405",
"recipient": {
"address": "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K",
"address_hash_bytes": "0x89f5fd1f719e4449c980de38e3504be6770a2698",
"address_version": 22
},
"sender": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26
},
"transfered_ustx": 10,
"txid": "85aa2106186723f3c4f1d8bb58e3a02746ca9be1be9f4be0c6557079e1f660e6",
"vtxindex": 10
}
}
```
- StackStx
```json
{
"StackStx": {
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"num_cycles": 10,
"reward_addr": "16Jswqk47s9PUcyCc88MMVwzgvHPvtEpf",
"sender": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26
},
"stacked_ustx": 10,
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10
}
}
```
- DelegateStx
```json
{
"DelegateStx": {
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"output": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26
},
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10
}
}
```
### `POST /new_burn_block`
This payload includes information about burn blocks as their sortitions are processed.

View File

@@ -20,6 +20,8 @@ use std::error;
use std::fmt;
use std::fs;
use std::io;
use serde::{Deserialize, Serialize};
use clarity::vm::types::PrincipalData;
use crate::burnchains::Burnchain;
use crate::burnchains::BurnchainBlockHeader;
@@ -42,7 +44,7 @@ use crate::chainstate::stacks::address::PoxAddress;
use crate::util_lib::db::DBConn;
use crate::util_lib::db::DBTx;
use crate::util_lib::db::Error as db_error;
use stacks_common::util::hash::Hash160;
use stacks_common::util::hash::{Hash160, hex_bytes, to_hex};
use stacks_common::util::hash::Sha512Trunc256Sum;
use stacks_common::util::secp256k1::MessageSignature;
use stacks_common::util::vrf::VRFPublicKey;
@@ -180,24 +182,31 @@ impl From<db_error> for Error {
#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)]
pub struct TransferStxOp {
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub sender: StacksAddress,
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub recipient: StacksAddress,
pub transfered_ustx: u128,
#[serde(deserialize_with = "memo_deserialize", serialize_with = "memo_serialize")]
pub memo: Vec<u8>,
// common to all transactions
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header
}
#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)]
pub struct StackStxOp {
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub sender: StacksAddress,
/// the PoX reward address.
/// NOTE: the address in .pox will be tagged as either p2pkh or p2sh; it's impossible to tell
/// if it's a segwit-p2sh since that looks identical to a p2sh address.
#[serde(serialize_with = "crate::chainstate::stacks::address::pox_addr_b58_serialize")]
#[serde(deserialize_with = "crate::chainstate::stacks::address::pox_addr_b58_deser")]
pub reward_addr: PoxAddress,
/// how many ustx this transaction locks
pub stacked_ustx: u128,
@@ -207,6 +216,7 @@ pub struct StackStxOp {
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header
}
@@ -214,24 +224,29 @@ pub struct StackStxOp {
pub struct PreStxOp {
/// the output address
/// (must be a legacy Bitcoin address)
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub output: StacksAddress,
// common to all transactions
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header
}
#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)]
pub struct LeaderBlockCommitOp {
#[serde(deserialize_with = "block_hh_deserialize", serialize_with = "block_hh_serialize")]
pub block_header_hash: BlockHeaderHash, // hash of Stacks block header (sha512/256)
#[serde(deserialize_with = "vrf_seed_deserialize", serialize_with = "vrf_seed_serialize")]
pub new_seed: VRFSeed, // new seed for this block
pub parent_block_ptr: u32, // block height of the block that contains the parent block hash
pub parent_vtxindex: u16, // offset in the parent block where the parent block hash can be found
pub key_block_ptr: u32, // pointer to the block that contains the leader key registration
pub key_vtxindex: u16, // offset in the block where the leader key can be found
#[serde(deserialize_with = "memo_deserialize", serialize_with = "memo_serialize")]
pub memo: Vec<u8>, // extra unused byte
/// how many burn tokens (e.g. satoshis) were committed to produce this block
@@ -247,6 +262,8 @@ pub struct LeaderBlockCommitOp {
pub apparent_sender: BurnchainSigner,
/// PoX/Burn outputs
#[serde(serialize_with = "crate::chainstate::stacks::address::pox_addr_vec_b58_serialize")]
#[serde(deserialize_with = "crate::chainstate::stacks::address::pox_addr_vec_b58_deser")]
pub commit_outs: Vec<PoxAddress>,
// PoX sunset burn
pub sunset_burn: u64,
@@ -255,19 +272,23 @@ pub struct LeaderBlockCommitOp {
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header
}
#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)]
pub struct LeaderKeyRegisterOp {
#[serde(deserialize_with = "consensus_hash_deserialize", serialize_with = "consensus_hash_serialize")]
pub consensus_hash: ConsensusHash, // consensus hash at time of issuance
pub public_key: VRFPublicKey, // EdDSA public key
#[serde(deserialize_with = "memo_deserialize", serialize_with = "memo_serialize")]
pub memo: Vec<u8>, // extra bytes in the op-return
// common to all transactions
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of burn chain block
}
@@ -291,12 +312,15 @@ pub struct UserBurnSupportOp {
#[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)]
pub struct DelegateStxOp {
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub sender: StacksAddress,
#[serde(deserialize_with = "stacks_addr_deserialize", serialize_with = "stacks_addr_serialize")]
pub delegate_to: StacksAddress,
/// a tuple representing the output index of the reward address in the BTC transaction,
// and the actual PoX reward address.
/// NOTE: the address in .pox-2 will be tagged as either p2pkh or p2sh; it's impossible to tell
/// if it's a segwit-p2sh since that looks identical to a p2sh address.
#[serde(deserialize_with = "reward_addr_deserialize", serialize_with = "reward_addr_serialize")]
pub reward_addr: Option<(u32, PoxAddress)>,
pub delegated_ustx: u128,
pub until_burn_height: Option<u64>,
@@ -305,6 +329,7 @@ pub struct DelegateStxOp {
pub txid: Txid, // transaction ID
pub vtxindex: u32, // index in the block where this tx occurs
pub block_height: u64, // block height at which this tx occurs
#[serde(deserialize_with = "hex_deserialize", serialize_with = "hex_serialize")]
pub burn_header_hash: BurnchainHeaderHash, // hash of the burn chain block header
}
@@ -447,3 +472,398 @@ pub fn parse_u32_from_be(bytes: &[u8]) -> Option<u32> {
pub fn parse_u16_from_be(bytes: &[u8]) -> Option<u16> {
bytes.try_into().ok().map(u16::from_be_bytes)
}
// serialization functions
fn hex_serialize<S: serde::Serializer>(bhh: &BurnchainHeaderHash, s: S) -> Result<S::Ok, S::Error> {
let inst = bhh.to_hex();
s.serialize_str(inst.as_str())
}
fn hex_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<BurnchainHeaderHash, D::Error> {
let inst_str = String::deserialize(d)?;
BurnchainHeaderHash::from_hex(&inst_str).map_err(serde::de::Error::custom)
}
fn block_hh_serialize<S: serde::Serializer>(bhh: &BlockHeaderHash, s: S) -> Result<S::Ok, S::Error> {
let inst = bhh.to_hex();
s.serialize_str(inst.as_str())
}
fn block_hh_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<BlockHeaderHash, D::Error> {
let inst_str = String::deserialize(d)?;
BlockHeaderHash::from_hex(&inst_str).map_err(serde::de::Error::custom)
}
fn vrf_seed_serialize<S: serde::Serializer>(seed: &VRFSeed, s: S) -> Result<S::Ok, S::Error> {
let inst = seed.to_hex();
s.serialize_str(inst.as_str())
}
fn vrf_seed_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<VRFSeed, D::Error> {
let inst_str = String::deserialize(d)?;
VRFSeed::from_hex(&inst_str).map_err(serde::de::Error::custom)
}
fn consensus_hash_serialize<S: serde::Serializer>(ch: &ConsensusHash, s: S) -> Result<S::Ok, S::Error> {
let inst = ch.to_hex();
s.serialize_str(inst.as_str())
}
fn consensus_hash_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<ConsensusHash, D::Error> {
let inst_str = String::deserialize(d)?;
ConsensusHash::from_hex(&inst_str).map_err(serde::de::Error::custom)
}
fn memo_serialize<S: serde::Serializer>(memo: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
let hex_inst = to_hex(memo);
let byte_str = format!("0x{}", hex_inst);
s.serialize_str(byte_str.as_str())
}
fn memo_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<Vec<u8>, D::Error> {
let bytes_str = String::deserialize(d)?;
let hex_inst = &bytes_str[2..];
hex_bytes(&hex_inst).map_err(serde::de::Error::custom)
}
#[derive(Serialize, Deserialize)]
struct StacksAddrJsonDisplay {
address: String,
#[serde(deserialize_with = "hash_160_deserialize", serialize_with = "hash_160_serialize")]
address_hash_bytes: Hash160,
address_version: u8,
}
fn hash_160_serialize<S: serde::Serializer>(hash: &Hash160, s: S) -> Result<S::Ok, S::Error> {
let hex_inst = to_hex(&hash.0);
let byte_str = format!("0x{}", hex_inst);
s.serialize_str(byte_str.as_str())
}
fn hash_160_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<Hash160, D::Error> {
let bytes_str = String::deserialize(d)?;
let hex_inst = &bytes_str[2..];
Hash160::from_hex(&hex_inst).map_err(serde::de::Error::custom)
}
fn stacks_addr_serialize<S: serde::Serializer>(addr: &StacksAddress, s: S) -> Result<S::Ok, S::Error> {
let addr_str = addr.to_string();
let addr_display = StacksAddrJsonDisplay {
address: addr_str,
address_hash_bytes: addr.bytes,
address_version: addr.version,
};
addr_display.serialize(s)
}
fn stacks_addr_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<StacksAddress, D::Error> {
let addr_display = StacksAddrJsonDisplay::deserialize(d)?;
Ok(StacksAddress {
version: addr_display.address_version,
bytes: addr_display.address_hash_bytes,
})
}
fn reward_addr_serialize<S: serde::Serializer>(addr: &Option<(u32, PoxAddress)>, s: S) -> Result<S::Ok, S::Error> {
match addr {
None => s.serialize_none(),
Some((index, pox_addr)) => {
let str_addr = pox_addr.clone().to_b58();
s.serialize_some(&(index, str_addr))
}
}
}
fn reward_addr_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<Option<(u32, PoxAddress)>, D::Error> {
let reward_addr: Option<(u32, String)> = Option::deserialize(d)?;
match reward_addr {
None => Ok(None),
Some((input, pox_add_str)) => {
let pox_addr = PoxAddress::from_b58(&pox_add_str).ok_or_else(|| serde::de::Error::custom("Failed to decode PoxAddress from string"))?;
Ok(Some((input, pox_addr)))
}
}
}
mod test {
use serde::{Deserialize, Serialize};
use serde_json::Serializer;
use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_MAINNET_SINGLESIG};
use stacks_common::types::Address;
use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, StacksAddress, VRFSeed};
use stacks_common::util::hash::Hash160;
use stacks_common::util::vrf::{VRFPrivateKey, VRFPublicKey};
use crate::burnchains::{BurnchainSigner, Txid};
use crate::chainstate::burn::operations::{DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp, StackStxOp, TransferStxOp, UserBurnSupportOp};
use crate::chainstate::stacks::address::PoxAddress;
#[test]
fn test_serialization_transfer_stx_op() {
let sender_addr = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2";
let sender = StacksAddress::from_string(sender_addr).unwrap();
let recipient_addr = "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K";
let recipient = StacksAddress::from_string(recipient_addr).unwrap();
let op = TransferStxOp {
sender,
recipient,
transfered_ustx: 10,
memo: vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"memo": "0x000102030405",
"recipient": {
"address": "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K",
"address_hash_bytes": "0x89f5fd1f719e4449c980de38e3504be6770a2698",
"address_version": 22,
},
"sender": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26,
},
"transfered_ustx": 10,
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = TransferStxOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
#[test]
fn test_serialization_stack_stx_op() {
let sender_addr = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2";
let sender = StacksAddress::from_string(sender_addr).unwrap();
let reward_addr = PoxAddress::Standard(
StacksAddress {
version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG,
bytes: Hash160([0x01; 20])
},
Some(AddressHashMode::SerializeP2PKH)
);
let op = StackStxOp {
sender,
reward_addr,
stacked_ustx: 10,
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
num_cycles: 10,
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"num_cycles": 10,
"reward_addr": "16Jswqk47s9PUcyCc88MMVwzgvHPvtEpf",
"sender": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26,
},
"stacked_ustx": 10,
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = StackStxOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
#[test]
fn test_serialization_pre_stx_op() {
let output_addr = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2";
let output = StacksAddress::from_string(output_addr).unwrap();
let op = PreStxOp {
output,
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"output": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26,
},
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = PreStxOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
#[test]
fn test_serialization_leader_block_commit_op() {
let pox_addr = PoxAddress::Standard(
StacksAddress {
version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG,
bytes: Hash160([0x01; 20])
},
Some(AddressHashMode::SerializeP2PKH)
);
let op = LeaderBlockCommitOp {
block_header_hash: BlockHeaderHash([0x10; 32]),
new_seed: VRFSeed([0x10; 32]),
parent_block_ptr: 10,
parent_vtxindex: 10,
key_block_ptr: 10,
key_vtxindex: 10,
memo: vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
input: (Txid([10u8; 32]), 10),
burn_parent_modulus: 10,
apparent_sender: BurnchainSigner("signer".to_string()),
commit_outs: vec![pox_addr.clone(), pox_addr],
sunset_burn: 10,
burn_fee: 10,
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"apparent_sender": "signer",
"block_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"block_height": 10,
"burn_fee": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"burn_parent_modulus": 10,
"commit_outs": ["16Jswqk47s9PUcyCc88MMVwzgvHPvtEpf", "16Jswqk47s9PUcyCc88MMVwzgvHPvtEpf"],
"input": ("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", 10),
"key_block_ptr": 10,
"key_vtxindex": 10,
"memo": "0x000102030405",
"new_seed": "1010101010101010101010101010101010101010101010101010101010101010",
"parent_block_ptr": 10,
"parent_vtxindex": 10,
"sunset_burn": 10,
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = LeaderBlockCommitOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
#[test]
fn test_serialization_leader_key_register_op() {
let public_key = VRFPublicKey::from_bytes(&[0x10; 32]).unwrap();
let op = LeaderKeyRegisterOp {
consensus_hash: ConsensusHash([0x10; 20]),
public_key,
memo: vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05],
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"consensus_hash": "1010101010101010101010101010101010101010",
"memo": "0x000102030405",
"public_key": "1010101010101010101010101010101010101010101010101010101010101010",
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = LeaderKeyRegisterOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
#[test]
fn test_serialization_delegate_stx_op() {
let sender_addr = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2";
let sender = StacksAddress::from_string(sender_addr).unwrap();
let delegate_to_addr = "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K";
let delegate_to = StacksAddress::from_string(delegate_to_addr).unwrap();
let pox_addr = PoxAddress::Standard(
StacksAddress {
version: C32_ADDRESS_VERSION_MAINNET_SINGLESIG,
bytes: Hash160([0x01; 20])
},
Some(AddressHashMode::SerializeP2PKH),
);
let op = DelegateStxOp {
sender,
delegate_to,
reward_addr: Some((10, pox_addr)),
delegated_ustx: 10,
until_burn_height: None,
txid: Txid([10u8; 32]),
vtxindex: 10,
block_height: 10,
burn_header_hash: BurnchainHeaderHash([0x10; 32]),
};
let serialized_json = serde_json::json!(&op);
let constructed_json = json!({
"block_height": 10,
"burn_header_hash": "1010101010101010101010101010101010101010101010101010101010101010",
"delegate_to": {
"address": "SP24ZBZ8ZE6F48JE9G3F3HRTG9FK7E2H6K2QZ3Q1K",
"address_hash_bytes": "0x89f5fd1f719e4449c980de38e3504be6770a2698",
"address_version": 22,
},
"delegated_ustx": 10,
"sender": {
"address": "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2",
"address_hash_bytes": "0xaf3f91f38aa21ade7e9f95efdbc4201eeb4cf0f8",
"address_version": 26,
},
"reward_addr": [10, "16Jswqk47s9PUcyCc88MMVwzgvHPvtEpf"],
"txid": "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a",
"until_burn_height": null,
"vtxindex": 10,
});
assert_json_eq!(serialized_json.clone(), constructed_json);
let deserialized = DelegateStxOp::deserialize(serialized_json).unwrap();
assert_eq!(op, deserialized);
}
}

View File

@@ -18,6 +18,8 @@ use std::cmp::{Ord, Ordering};
use std::io::prelude::*;
use std::io::{Read, Write};
use std::{fmt, io};
use serde::{Deserialize, Deserializer, Serializer};
use serde::ser::SerializeSeq;
use crate::burnchains::bitcoin::address::{
legacy_address_type_to_version_byte, legacy_version_byte_to_address_type, to_b58_version_byte,
@@ -92,6 +94,44 @@ pub enum PoxAddress {
Addr32(bool, PoxAddressType32, [u8; 32]),
}
/// Serializes a PoxAddress as a B58 check encoded address or a bech32 address
pub fn pox_addr_b58_serialize<S: Serializer>(
input: &PoxAddress,
ser: S,
) -> Result<S::Ok, S::Error> {
ser.serialize_str(&input.clone().to_b58())
}
/// Deserializes a PoxAddress from a B58 check encoded address or a bech32 address
pub fn pox_addr_b58_deser<'de, D: Deserializer<'de>>(deser: D) -> Result<PoxAddress, D::Error> {
let string_repr = String::deserialize(deser)?;
PoxAddress::from_b58(&string_repr)
.ok_or_else(|| serde::de::Error::custom("Failed to decode PoxAddress from string"))
}
/// Serializes each PoxAddress in a vector as a B58 check encoded address or a bech32 address
pub fn pox_addr_vec_b58_serialize<S: Serializer>(
input: &Vec<PoxAddress>,
ser: S,
) -> Result<S::Ok, S::Error> {
let mut seq = ser.serialize_seq(Some(input.len()))?;
for element in input {
seq.serialize_element(&element.clone().to_b58())?;
}
seq.end()
}
/// Deserializes each PoxAddress in a vector from a B58 check encoded address or a bech32 address
pub fn pox_addr_vec_b58_deser<'de, D: Deserializer<'de>>(deser: D) -> Result<Vec<PoxAddress>, D::Error> {
let mut pox_vec = vec![];
let pox_vec_ser: Vec<String> = Vec::deserialize(deser)?;
for elem in pox_vec_ser {
pox_vec.push(PoxAddress::from_b58(&elem)
.ok_or_else(|| serde::de::Error::custom("Failed to decode PoxAddress from string"))?);
}
Ok(pox_vec)
}
impl std::fmt::Display for PoxAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_db_string())
@@ -407,6 +447,16 @@ impl PoxAddress {
}
}
// Convert from a B58 encoded bitcoin address
pub fn from_b58(input: &str) -> Option<Self> {
let btc_addr = BitcoinAddress::from_string(input)?;
PoxAddress::try_from_bitcoin_output(&BitcoinTxOutput {
address: btc_addr,
units: 0,
})
}
/// Convert this PoxAddress into a Bitcoin tx output
pub fn to_bitcoin_tx_out(&self, value: u64) -> TxOut {
match *self {

View File

@@ -1701,8 +1701,6 @@ fn stx_transfer_btc_integration_test() {
for tx in transactions.iter() {
let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap();
if raw_tx == "0x00" {
let dbg = format!("{:#}", tx);
info!("TX-BTC {}", dbg);
let burnchain_op = tx.get("burnchain_op").unwrap().as_object().unwrap();
if !burnchain_op.contains_key("TransferStx") {
panic!("unexpected btc transaction type");
@@ -1712,7 +1710,6 @@ fn stx_transfer_btc_integration_test() {
}
}
assert!(found_btc_tx);
assert!(false);
channel.stop_chains_coordinator();
}