hal_simplicity/actions/
block.rs

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
158/// Create a block from block info.
159pub 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
180/// Decode a raw block and return block info or header info.
181pub 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}