mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-23 03:20:19 +08:00
added docs, serialization functions, tests, and json formatting for btc ops is readable
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user