waves_rust/model/rs/
blocks.rs

1use crate::error::{Error, Result};
2use crate::model::{Address, Base58String, SignedTransaction};
3use crate::util::JsonDeserializer;
4use serde_json::Value;
5use std::borrow::Borrow;
6
7#[derive(Clone, Eq, PartialEq, Debug)]
8pub struct BlockHeaders {
9    version: u8,
10    timestamp: u64,
11    reference: Base58String,
12    nxt_consensus: NxtConsensus,
13    transactions_root: Base58String,
14    id: Base58String,
15    features: Vec<u32>,
16    desired_reward: i64,
17    generator: Address,
18    signature: Base58String,
19    blocksize: u32,
20    transaction_count: u32,
21    height: u32,
22    total_fee: u64,
23    reward: u64,
24    vrf: Base58String,
25}
26
27#[allow(clippy::too_many_arguments)]
28impl BlockHeaders {
29    pub fn new(
30        version: u8,
31        timestamp: u64,
32        reference: Base58String,
33        nxt_consensus: NxtConsensus,
34        transactions_root: Base58String,
35        id: Base58String,
36        features: Vec<u32>,
37        desired_reward: i64,
38        generator: Address,
39        signature: Base58String,
40        blocksize: u32,
41        transaction_count: u32,
42        height: u32,
43        total_fee: u64,
44        reward: u64,
45        vrf: Base58String,
46    ) -> Self {
47        Self {
48            version,
49            timestamp,
50            reference,
51            nxt_consensus,
52            transactions_root,
53            id,
54            features,
55            desired_reward,
56            generator,
57            signature,
58            blocksize,
59            transaction_count,
60            height,
61            total_fee,
62            reward,
63            vrf,
64        }
65    }
66
67    pub fn version(&self) -> u8 {
68        self.version
69    }
70
71    pub fn timestamp(&self) -> u64 {
72        self.timestamp
73    }
74
75    pub fn reference(&self) -> Base58String {
76        self.reference.clone()
77    }
78
79    pub fn nxt_consensus(&self) -> NxtConsensus {
80        self.nxt_consensus.clone()
81    }
82
83    pub fn transactions_root(&self) -> Base58String {
84        self.transactions_root.clone()
85    }
86
87    pub fn id(&self) -> Base58String {
88        self.id.clone()
89    }
90
91    pub fn features(&self) -> Vec<u32> {
92        self.features.clone()
93    }
94
95    pub fn desired_reward(&self) -> i64 {
96        self.desired_reward
97    }
98
99    pub fn generator(&self) -> Address {
100        self.generator.clone()
101    }
102
103    pub fn signature(&self) -> Base58String {
104        self.signature.clone()
105    }
106
107    pub fn blocksize(&self) -> u32 {
108        self.blocksize
109    }
110
111    pub fn transaction_count(&self) -> u32 {
112        self.transaction_count
113    }
114
115    pub fn height(&self) -> u32 {
116        self.height
117    }
118
119    pub fn total_fee(&self) -> u64 {
120        self.total_fee
121    }
122
123    pub fn reward(&self) -> u64 {
124        self.reward
125    }
126
127    pub fn vrf(&self) -> Base58String {
128        self.vrf.clone()
129    }
130}
131
132impl TryFrom<&Value> for BlockHeaders {
133    type Error = Error;
134
135    fn try_from(value: &Value) -> Result<Self> {
136        let version = JsonDeserializer::safe_to_int_from_field(value, "version")?;
137        let timestamp = JsonDeserializer::safe_to_int_from_field(value, "timestamp")?;
138        let reference = JsonDeserializer::safe_to_string_from_field(value, "reference")?;
139        let transactions_root = match value["transactionsRoot"].as_str() {
140            Some(val) => Base58String::from_string(val)?,
141            None => Base58String::empty(),
142        };
143        let id = JsonDeserializer::safe_to_string_from_field(value, "id")?;
144        let features = match value["features"].as_array() {
145            Some(array) => array
146                .iter()
147                .filter_map(|value| value.as_i64())
148                .map(|feature| feature as u32)
149                .collect(),
150            None => vec![],
151        };
152
153        let desired_reward = value["desiredReward"].as_i64().unwrap_or(0);
154        let generator = JsonDeserializer::safe_to_string_from_field(value, "generator")?;
155        let signature = JsonDeserializer::safe_to_string_from_field(value, "signature")?;
156        let blocksize = JsonDeserializer::safe_to_int_from_field(value, "blocksize")?;
157        let transaction_count =
158            JsonDeserializer::safe_to_int_from_field(value, "transactionCount")?;
159        let height = JsonDeserializer::safe_to_int_from_field(value, "height")?;
160        let total_fee = JsonDeserializer::safe_to_int_from_field(value, "totalFee")?;
161        let reward = value["reward"].as_i64().unwrap_or(0);
162
163        let vrf = value["VRF"].as_str().unwrap_or("");
164        Ok(BlockHeaders {
165            version: version as u8,
166            timestamp: timestamp as u64,
167            reference: Base58String::from_string(&reference)?,
168            nxt_consensus: value["nxt-consensus"].borrow().try_into()?,
169            transactions_root,
170            id: Base58String::from_string(&id)?,
171            features,
172            desired_reward,
173            generator: Address::from_string(&generator)?,
174            signature: Base58String::from_string(&signature)?,
175            blocksize: blocksize as u32,
176            transaction_count: transaction_count as u32,
177            height: height as u32,
178            total_fee: total_fee as u64,
179            reward: reward as u64,
180            vrf: Base58String::from_string(vrf)?,
181        })
182    }
183}
184
185#[derive(Clone, Eq, PartialEq, Debug)]
186pub struct NxtConsensus {
187    base_target: u32,
188    generation_signature: Base58String,
189}
190
191impl NxtConsensus {
192    pub fn new(base_target: u32, generation_signature: Base58String) -> Self {
193        Self {
194            base_target,
195            generation_signature,
196        }
197    }
198
199    pub fn base_target(&self) -> u32 {
200        self.base_target
201    }
202
203    pub fn generation_signature(&self) -> Base58String {
204        self.generation_signature.clone()
205    }
206}
207
208impl TryFrom<&Value> for NxtConsensus {
209    type Error = Error;
210
211    fn try_from(value: &Value) -> Result<Self> {
212        let base_target = JsonDeserializer::safe_to_int_from_field(value, "base-target")?;
213        let generation_signature =
214            JsonDeserializer::safe_to_string_from_field(value, "generation-signature")?;
215
216        Ok(NxtConsensus {
217            base_target: base_target as u32,
218            generation_signature: Base58String::from_string(&generation_signature)?,
219        })
220    }
221}
222
223#[derive(Clone, Eq, PartialEq, Debug)]
224pub struct Block {
225    block_headers: BlockHeaders,
226    fee: u64,
227    transactions: Vec<SignedTransaction>,
228}
229
230impl Block {
231    pub fn new(
232        block_headers: BlockHeaders,
233        fee: u64,
234        transactions: Vec<SignedTransaction>,
235    ) -> Self {
236        Self {
237            block_headers,
238            fee,
239            transactions,
240        }
241    }
242
243    pub fn block_headers(&self) -> BlockHeaders {
244        self.block_headers.clone()
245    }
246
247    pub fn fee(&self) -> u64 {
248        self.fee
249    }
250
251    pub fn transactions(&self) -> Vec<SignedTransaction> {
252        self.transactions.clone()
253    }
254}
255
256impl TryFrom<&Value> for Block {
257    type Error = Error;
258
259    fn try_from(value: &Value) -> Result<Self> {
260        let block_headers = value.try_into()?;
261        let fee = JsonDeserializer::safe_to_int_from_field(value, "fee")?;
262
263        let transactions: Vec<SignedTransaction> =
264            JsonDeserializer::safe_to_array_from_field(value, "transactions")?
265                .iter()
266                .map(|tx| tx.try_into())
267                .collect::<Result<Vec<SignedTransaction>>>()?;
268        Ok(Self {
269            block_headers,
270            fee: fee as u64,
271            transactions,
272        })
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use crate::error::Result;
279    use crate::model::{Block, BlockHeaders, ByteString};
280    use serde_json::Value;
281    use std::borrow::Borrow;
282    use std::fs;
283
284    #[test]
285    pub fn block_headers_from_response() -> Result<()> {
286        let data = fs::read_to_string("./tests/resources/blocks/block_headers_rs.json")
287            .expect("Unable to read file");
288        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
289
290        let block_headers: BlockHeaders = json.borrow().try_into()?;
291
292        assert_eq!(5, block_headers.version());
293        assert_eq!(1662965069859, block_headers.timestamp());
294        assert_eq!(
295            "FDVU5z5JcjU1rU6BV7MassmFwzfiabxVUBcDxzJjZTNu",
296            block_headers.reference().encoded()
297        );
298        assert_eq!(288, block_headers.nxt_consensus().base_target());
299        assert_eq!(
300            "2DzzTwTrDvNnKa5BnkQyuGJ6rAmCTxrKwQNdAEiRPPiY1WkrCy5ge5FDAWsMPh3eAk3sRQq3iVB8ZVGFkP8uJU65AfR4rkQw687yNcA3wbsDsDjsoyir1nGFCWkA34jv5cUN", 
301                   block_headers.nxt_consensus().generation_signature().encoded());
302
303        assert_eq!(
304            "HW2HdCDGFX1cKQC9VQHPbCi5S87jzB43m8bM1yvbjxN6",
305            block_headers.transactions_root().encoded()
306        );
307        assert_eq!(
308            "7FjvruZHvR3mi9YvTaiGQ93uvdHbarcL54ynrS6jN1Vq",
309            block_headers.id().encoded()
310        );
311        assert_eq!(3, block_headers.features()[0]);
312        assert_eq!(-1, block_headers.desired_reward());
313        assert_eq!(
314            "3Mxv6Dpa1qRuyQBRFg3GwUaf3rcjHqWwNmC",
315            block_headers.generator().encoded()
316        );
317        assert_eq!("3BCrNxGjwoxRMX2vxshN2K5ucSzt8huwvkHK54qWnp6UVQLBKx1TaidWAoxtswWPxbLTdmkWAMrCEdUVUZw4o8Zr", block_headers.signature().encoded());
318        assert_eq!(869, block_headers.blocksize());
319        assert_eq!(1, block_headers.transaction_count());
320        assert_eq!(2225531, block_headers.height());
321        assert_eq!(500000, block_headers.total_fee());
322        assert_eq!(600000000, block_headers.reward());
323        assert_eq!(
324            "BaHPQwcWxXPaaUiRgavbEg5hkAvzWdNrLYV3x5JEDgpJ",
325            block_headers.vrf().encoded()
326        );
327        Ok(())
328    }
329
330    #[test]
331    pub fn block_at_height_response() -> Result<()> {
332        let data = fs::read_to_string("./tests/resources/blocks/block_rs.json")
333            .expect("Unable to read file");
334        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
335
336        let block: Block = json.borrow().try_into()?;
337        let block_headers = block.block_headers();
338
339        assert_eq!(5, block_headers.version());
340        assert_eq!(1662965069859, block_headers.timestamp());
341        assert_eq!(
342            "FDVU5z5JcjU1rU6BV7MassmFwzfiabxVUBcDxzJjZTNu",
343            block_headers.reference().encoded()
344        );
345        assert_eq!(288, block_headers.nxt_consensus().base_target());
346        assert_eq!(
347            "2DzzTwTrDvNnKa5BnkQyuGJ6rAmCTxrKwQNdAEiRPPiY1WkrCy5ge5FDAWsMPh3eAk3sRQq3iVB8ZVGFkP8uJU65AfR4rkQw687yNcA3wbsDsDjsoyir1nGFCWkA34jv5cUN",
348            block_headers.nxt_consensus().generation_signature().encoded());
349
350        assert_eq!(
351            "HW2HdCDGFX1cKQC9VQHPbCi5S87jzB43m8bM1yvbjxN6",
352            block_headers.transactions_root().encoded()
353        );
354        assert_eq!(
355            "7FjvruZHvR3mi9YvTaiGQ93uvdHbarcL54ynrS6jN1Vq",
356            block_headers.id().encoded()
357        );
358        assert_eq!(3, block_headers.features()[0]);
359        assert_eq!(-1, block_headers.desired_reward());
360        assert_eq!(
361            "3Mxv6Dpa1qRuyQBRFg3GwUaf3rcjHqWwNmC",
362            block_headers.generator().encoded()
363        );
364        assert_eq!("3BCrNxGjwoxRMX2vxshN2K5ucSzt8huwvkHK54qWnp6UVQLBKx1TaidWAoxtswWPxbLTdmkWAMrCEdUVUZw4o8Zr", block_headers.signature().encoded());
365        assert_eq!(869, block_headers.blocksize());
366        assert_eq!(1, block_headers.transaction_count());
367        assert_eq!(2225531, block_headers.height());
368        assert_eq!(500000, block_headers.total_fee());
369        assert_eq!(600000000, block_headers.reward());
370        assert_eq!(
371            "BaHPQwcWxXPaaUiRgavbEg5hkAvzWdNrLYV3x5JEDgpJ",
372            block_headers.vrf().encoded()
373        );
374
375        assert_eq!(500000, block.fee());
376        assert_eq!(19, block.transactions().len());
377
378        let genesis = &block.transactions[0];
379        assert_eq!(
380            "3zpi4i5SeCoaiCBn1iuTUvCc5aahvtabqXBTrCXy1Y3ujUbJo56VVv6n4HQtcwiFapvg3BKV6stb5QkxsBrudTKZ", 
381            genesis.id()?.encoded()
382        );
383        let payment = &block.transactions[1];
384        assert_eq!(
385            "3MBsS7S42PVEM8c1XxLsGsxzhitPsyaazDs1QoE26pCTHdRMYRv7n984wmjSFP863iZ2GR28aunSVvPC8sooEpbP",
386            payment.id()?.encoded()
387        );
388        let issue = &block.transactions[2];
389        assert_eq!(
390            "3kuZKAeyjcqavmezy86sWCAeXrgt3HBKa4HA8CZdT8nH",
391            issue.id()?.encoded()
392        );
393        let transfer = &block.transactions[3];
394        assert_eq!(
395            "DBozd2VWYe1FDkrdQnJgvcxh9B6mL872onqpSCjF4a7t",
396            transfer.id()?.encoded()
397        );
398        // let reissue = &block.transactions[4];
399        // assert_eq!(
400        //     "44seokQaBquAwDweKC4mbmHvmu2heWrUhKNGUakwZxRf",
401        //     reissue.id()?.encoded()
402        // );
403        let burn = &block.transactions[5];
404        assert_eq!(
405            "7Ruo9tnYTuBKTRwbSfG2TLooP4v6pz8SkTx1hvCgfJLU",
406            burn.id()?.encoded()
407        );
408
409        println!("{:#?}", block);
410
411        Ok(())
412    }
413}