Skip to main content

chia_query/peer/
translate.rs

1//! Conversions between `chia_protocol` peer-protocol types and our public
2//! response types.
3
4use chia::protocol::{Bytes32, CoinState, HeaderBlock, Program, RespondAdditions, RespondRemovals};
5
6use crate::types::{
7    AdditionsAndRemovals, BlockRecord, ChiaQueryError, Coin, CoinRecord, CoinSpend, FeeEstimate,
8    TxStatus,
9};
10
11// ---------------------------------------------------------------------------
12// Hex utilities
13// ---------------------------------------------------------------------------
14
15pub fn parse_hex(s: &str) -> Result<Vec<u8>, ChiaQueryError> {
16    let s = s.strip_prefix("0x").unwrap_or(s);
17    hex::decode(s).map_err(|e| ChiaQueryError::InvalidRequest(format!("bad hex: {e}")))
18}
19
20pub fn parse_bytes32(s: &str) -> Result<Bytes32, ChiaQueryError> {
21    let bytes = parse_hex(s)?;
22    let arr: [u8; 32] = bytes
23        .try_into()
24        .map_err(|_| ChiaQueryError::InvalidRequest("expected 32 bytes".into()))?;
25    Ok(Bytes32::new(arr))
26}
27
28pub fn hex32(b: &Bytes32) -> String {
29    format!("0x{}", hex::encode(b.as_ref()))
30}
31
32pub fn hex_bytes(b: &[u8]) -> String {
33    format!("0x{}", hex::encode(b))
34}
35
36// ---------------------------------------------------------------------------
37// CoinState -> CoinRecord
38// ---------------------------------------------------------------------------
39
40pub fn coin_state_to_record(cs: &CoinState) -> CoinRecord {
41    CoinRecord {
42        coin: Coin::from_protocol(&cs.coin),
43        confirmed_block_index: cs.created_height.unwrap_or(0),
44        spent_block_index: cs.spent_height.unwrap_or(0),
45        spent: cs.spent_height.is_some(),
46        // These fields are not available via the peer protocol.
47        coinbase: false,
48        timestamp: 0,
49    }
50}
51
52pub fn coin_states_to_records(states: &[CoinState]) -> Vec<CoinRecord> {
53    states.iter().map(coin_state_to_record).collect()
54}
55
56// ---------------------------------------------------------------------------
57// Peer puzzle-and-solution -> our CoinSpend
58// ---------------------------------------------------------------------------
59
60pub fn make_coin_spend(
61    coin: &chia::protocol::Coin,
62    puzzle: &Program,
63    solution: &Program,
64) -> CoinSpend {
65    CoinSpend {
66        coin: Coin::from_protocol(coin),
67        puzzle_reveal: hex_bytes(puzzle.as_ref()),
68        solution: hex_bytes(solution.as_ref()),
69    }
70}
71
72// ---------------------------------------------------------------------------
73// Fee estimates (peer response is minimal; fill in defaults for fields only
74// available through the full-node RPC).
75// ---------------------------------------------------------------------------
76
77pub fn make_fee_estimate(estimates: Vec<f64>, target_times: Vec<u64>) -> FeeEstimate {
78    FeeEstimate {
79        estimates,
80        target_times,
81        current_fee_rate: 0.0,
82        mempool_size: 0,
83        mempool_fees: 0,
84        mempool_max_size: 0,
85        num_spends: 0,
86        full_node_synced: false,
87        peak_height: 0,
88        last_peak_timestamp: 0,
89        last_block_cost: 0,
90        fees_last_block: 0,
91        fee_rate_last_block: 0.0,
92        last_tx_block_height: 0,
93        node_time_utc: 0,
94    }
95}
96
97// ---------------------------------------------------------------------------
98// TransactionAck -> TxStatus
99// ---------------------------------------------------------------------------
100
101pub fn ack_to_tx_status(status: u8) -> TxStatus {
102    let label = match status {
103        1 => "SUCCESS",
104        2 => "PENDING",
105        3 => "FAILED",
106        _ => "UNKNOWN",
107    };
108    TxStatus {
109        status: label.to_string(),
110        success: status == 1 || status == 2,
111    }
112}
113
114// ---------------------------------------------------------------------------
115// HeaderBlock -> BlockRecord
116// ---------------------------------------------------------------------------
117
118pub fn header_block_to_block_record(hb: &HeaderBlock) -> BlockRecord {
119    let rcb = &hb.reward_chain_block;
120    let foliage = &hb.foliage;
121
122    let timestamp = hb.foliage_transaction_block.as_ref().map(|ft| ft.timestamp);
123
124    BlockRecord {
125        header_hash: hex32(&foliage.reward_block_hash),
126        height: rcb.height,
127        weight: rcb.weight as u64,
128        prev_hash: hex32(&foliage.prev_block_hash),
129        total_iters: rcb.total_iters as u64,
130        signage_point_index: rcb.signage_point_index,
131        farmer_puzzle_hash: hex32(&foliage.foliage_block_data.farmer_reward_puzzle_hash),
132        pool_puzzle_hash: String::new(), // pool target is in PoolTarget, not a plain hash
133        timestamp,
134        fees: None, // not available from the header alone
135        extra: serde_json::Value::Null,
136    }
137}
138
139// ---------------------------------------------------------------------------
140// RequestAdditions / RequestRemovals -> AdditionsAndRemovals
141// ---------------------------------------------------------------------------
142
143pub fn additions_removals_to_response(
144    additions_resp: &RespondAdditions,
145    removals_resp: &RespondRemovals,
146    height: u32,
147) -> AdditionsAndRemovals {
148    let additions: Vec<CoinRecord> = additions_resp
149        .coins
150        .iter()
151        .flat_map(|(_ph, coins)| {
152            coins.iter().map(|c| CoinRecord {
153                coin: Coin::from_protocol(c),
154                confirmed_block_index: height,
155                spent_block_index: 0,
156                spent: false,
157                coinbase: false,
158                timestamp: 0,
159            })
160        })
161        .collect();
162
163    let removals: Vec<CoinRecord> = removals_resp
164        .coins
165        .iter()
166        .filter_map(|(_name, maybe_coin)| {
167            maybe_coin.as_ref().map(|c| CoinRecord {
168                coin: Coin::from_protocol(c),
169                confirmed_block_index: 0,
170                spent_block_index: height,
171                spent: true,
172                coinbase: false,
173                timestamp: 0,
174            })
175        })
176        .collect();
177
178    AdditionsAndRemovals {
179        additions,
180        removals,
181    }
182}