1use elements::encode::deserialize;
2use elements::{dynafed, Block, BlockExtData, BlockHeader};
3
4use crate::block::{BlockHeaderInfo, BlockInfo, ParamsInfo, ParamsType};
5use crate::Network;
6
7#[derive(Debug, serde::Serialize)]
8#[serde(untagged)]
9pub enum BlockDecodeOutput {
10 Info(BlockInfo),
11 Header(BlockHeaderInfo),
12}
13
14#[derive(Debug, thiserror::Error)]
15pub enum BlockError {
16 #[error("can't provide transactions both in JSON and raw.")]
17 ConflictingTransactions,
18
19 #[error("no transactions provided.")]
20 NoTransactions,
21
22 #[error("failed to deserialize transaction: {0}")]
23 TransactionDeserialize(super::tx::TxError),
24
25 #[error("invalid raw transaction: {0}")]
26 InvalidRawTransaction(elements::encode::Error),
27
28 #[error("invalid block format: {0}")]
29 BlockDeserialize(elements::encode::Error),
30
31 #[error("could not decode raw block hex: {0}")]
32 CouldNotDecodeRawBlockHex(hex::FromHexError),
33
34 #[error("invalid json JSON input: {0}")]
35 InvalidJsonInput(serde_json::Error),
36
37 #[error("{field} missing in {context}")]
38 MissingField {
39 field: String,
40 context: String,
41 },
42}
43
44fn create_params(info: ParamsInfo) -> Result<dynafed::Params, BlockError> {
45 match info.params_type {
46 ParamsType::Null => Ok(dynafed::Params::Null),
47 ParamsType::Compact => Ok(dynafed::Params::Compact {
48 signblockscript: info
49 .signblockscript
50 .ok_or_else(|| BlockError::MissingField {
51 field: "signblockscript".to_string(),
52 context: "compact params".to_string(),
53 })?
54 .0
55 .into(),
56 signblock_witness_limit: info.signblock_witness_limit.ok_or_else(|| {
57 BlockError::MissingField {
58 field: "signblock_witness_limit".to_string(),
59 context: "compact params".to_string(),
60 }
61 })?,
62 elided_root: info.elided_root.ok_or_else(|| BlockError::MissingField {
63 field: "elided_root".to_string(),
64 context: "compact params".to_string(),
65 })?,
66 }),
67 ParamsType::Full => Ok(dynafed::Params::Full(dynafed::FullParams::new(
68 info.signblockscript
69 .ok_or_else(|| BlockError::MissingField {
70 field: "signblockscript".to_string(),
71 context: "full params".to_string(),
72 })?
73 .0
74 .into(),
75 info.signblock_witness_limit.ok_or_else(|| BlockError::MissingField {
76 field: "signblock_witness_limit".to_string(),
77 context: "full params".to_string(),
78 })?,
79 info.fedpeg_program
80 .ok_or_else(|| BlockError::MissingField {
81 field: "fedpeg_program".to_string(),
82 context: "full params".to_string(),
83 })?
84 .0
85 .into(),
86 info.fedpeg_script
87 .ok_or_else(|| BlockError::MissingField {
88 field: "fedpeg_script".to_string(),
89 context: "full params".to_string(),
90 })?
91 .0,
92 info.extension_space
93 .ok_or_else(|| BlockError::MissingField {
94 field: "extension space".to_string(),
95 context: "full params".to_string(),
96 })?
97 .into_iter()
98 .map(|b| b.0)
99 .collect(),
100 ))),
101 }
102}
103
104fn create_block_header(info: BlockHeaderInfo) -> Result<BlockHeader, BlockError> {
105 Ok(BlockHeader {
106 version: info.version,
107 prev_blockhash: info.previous_block_hash,
108 merkle_root: info.merkle_root,
109 time: info.time,
110 height: info.height,
111 ext: if info.dynafed {
112 BlockExtData::Dynafed {
113 current: create_params(info.dynafed_current.ok_or_else(|| {
114 BlockError::MissingField {
115 field: "current".to_string(),
116 context: "dynafed params".to_string(),
117 }
118 })?)?,
119 proposed: create_params(info.dynafed_proposed.ok_or_else(|| {
120 BlockError::MissingField {
121 field: "proposed".to_string(),
122 context: "dynafed params".to_string(),
123 }
124 })?)?,
125 signblock_witness: info
126 .dynafed_witness
127 .ok_or_else(|| BlockError::MissingField {
128 field: "witness".to_string(),
129 context: "dynafed params".to_string(),
130 })?
131 .into_iter()
132 .map(|b| b.0)
133 .collect(),
134 }
135 } else {
136 BlockExtData::Proof {
137 challenge: info
138 .legacy_challenge
139 .ok_or_else(|| BlockError::MissingField {
140 field: "challenge".to_string(),
141 context: "proof params".to_string(),
142 })?
143 .0
144 .into(),
145 solution: info
146 .legacy_solution
147 .ok_or_else(|| BlockError::MissingField {
148 field: "solution".to_string(),
149 context: "proof params".to_string(),
150 })?
151 .0
152 .into(),
153 }
154 },
155 })
156}
157
158pub fn block_create(info: BlockInfo) -> Result<Block, BlockError> {
160 let header = create_block_header(info.header)?;
161 let txdata = match (info.transactions, info.raw_transactions) {
162 (Some(_), Some(_)) => return Err(BlockError::ConflictingTransactions),
163 (None, None) => return Err(BlockError::NoTransactions),
164 (Some(infos), None) => infos
165 .into_iter()
166 .map(super::tx::tx_create)
167 .collect::<Result<Vec<_>, _>>()
168 .map_err(BlockError::TransactionDeserialize)?,
169 (None, Some(raws)) => raws
170 .into_iter()
171 .map(|r| deserialize(&r.0).map_err(BlockError::InvalidRawTransaction))
172 .collect::<Result<Vec<_>, _>>()?,
173 };
174 Ok(Block {
175 header,
176 txdata,
177 })
178}
179
180pub fn block_decode(
182 raw_block_hex: &str,
183 network: Network,
184 txids_only: bool,
185) -> Result<BlockDecodeOutput, BlockError> {
186 use crate::GetInfo;
187
188 let raw_block = hex::decode(raw_block_hex).map_err(BlockError::CouldNotDecodeRawBlockHex)?;
189
190 if txids_only {
191 let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
192 let info = BlockInfo {
193 header: block.header.get_info(network),
194 txids: Some(block.txdata.iter().map(|t| t.txid()).collect()),
195 transactions: None,
196 raw_transactions: None,
197 };
198 Ok(BlockDecodeOutput::Info(info))
199 } else {
200 let header: BlockHeader = match deserialize(&raw_block) {
201 Ok(header) => header,
202 Err(_) => {
203 let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
204 block.header
205 }
206 };
207 let info = header.get_info(network);
208 Ok(BlockDecodeOutput::Header(info))
209 }
210}