zebra_rpc/methods/types/get_block_template/
proposal.rs1use std::{num::ParseIntError, str::FromStr, sync::Arc};
6
7use zebra_chain::{
8 block::{self, Block, Height},
9 parameters::{Network, NetworkUpgrade},
10 serialization::{DateTime32, SerializationError, ZcashDeserializeInto},
11 work::equihash::Solution,
12};
13use zebra_node_services::BoxError;
14
15use crate::methods::types::{
16 default_roots::DefaultRoots,
17 get_block_template::{BlockTemplateResponse, GetBlockTemplateResponse},
18};
19
20#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
29#[serde(untagged, rename_all = "kebab-case")]
30pub enum BlockProposalResponse {
31 Rejected(String),
36
37 Valid,
39}
40
41impl BlockProposalResponse {
42 pub fn rejected<S: ToString>(_kind: S, error: BoxError) -> Self {
46 let error_kebab1 = format!("{error:?}")
48 .replace(|c: char| !c.is_alphanumeric(), "-")
49 .to_ascii_lowercase();
50 let mut error_v: Vec<char> = error_kebab1.chars().collect();
52 error_v.dedup_by(|a, b| a == &'-' && b == &'-');
53 let error_kebab2: String = error_v.into_iter().collect();
54 let final_error = error_kebab2.trim_matches('-');
56
57 BlockProposalResponse::Rejected(final_error.to_string())
58 }
59
60 pub fn is_valid(&self) -> bool {
62 matches!(self, Self::Valid)
63 }
64}
65
66impl From<BlockProposalResponse> for GetBlockTemplateResponse {
67 fn from(proposal_response: BlockProposalResponse) -> Self {
68 Self::ProposalMode(proposal_response)
69 }
70}
71
72impl From<BlockTemplateResponse> for GetBlockTemplateResponse {
73 fn from(template: BlockTemplateResponse) -> Self {
74 Self::TemplateMode(Box::new(template))
75 }
76}
77
78#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
80pub enum BlockTemplateTimeSource {
81 #[default]
84 CurTime,
85
86 MinTime,
88
89 MaxTime,
91
92 Clamped(DateTime32),
94
95 ClampedNow,
97
98 Raw(DateTime32),
102
103 RawNow,
107}
108
109impl BlockTemplateTimeSource {
110 pub fn time_from_template(&self, template: &BlockTemplateResponse) -> DateTime32 {
112 use BlockTemplateTimeSource::*;
113
114 match self {
115 CurTime => template.cur_time,
116 MinTime => template.min_time,
117 MaxTime => template.max_time,
118 Clamped(time) => (*time).clamp(template.min_time, template.max_time),
119 ClampedNow => DateTime32::now().clamp(template.min_time, template.max_time),
120 Raw(time) => *time,
121 RawNow => DateTime32::now(),
122 }
123 }
124
125 pub fn uses_max_time(&self) -> bool {
127 use BlockTemplateTimeSource::*;
128
129 match self {
130 CurTime | MinTime => false,
131 MaxTime | Clamped(_) | ClampedNow => true,
132 Raw(_) | RawNow => false,
133 }
134 }
135
136 pub fn valid_sources() -> impl IntoIterator<Item = BlockTemplateTimeSource> {
138 use BlockTemplateTimeSource::*;
139
140 [CurTime, MinTime, MaxTime, ClampedNow].into_iter()
141 }
142}
143
144impl FromStr for BlockTemplateTimeSource {
145 type Err = ParseIntError;
146
147 fn from_str(s: &str) -> Result<Self, Self::Err> {
148 use BlockTemplateTimeSource::*;
149
150 match s.to_lowercase().as_str() {
151 "curtime" => Ok(CurTime),
152 "mintime" => Ok(MinTime),
153 "maxtime" => Ok(MaxTime),
154 "clampednow" => Ok(ClampedNow),
155 "rawnow" => Ok(RawNow),
156 s => match s.strip_prefix("raw") {
157 Some(raw_value) => Ok(Raw(raw_value.parse()?)),
159 None => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)),
162 },
163 }
164 }
165}
166
167pub fn proposal_block_from_template(
171 template: &BlockTemplateResponse,
172 time_source: impl Into<Option<BlockTemplateTimeSource>>,
173 net: &Network,
174) -> Result<Block, SerializationError> {
175 let BlockTemplateResponse {
176 version,
177 height,
178 previous_block_hash,
179 default_roots:
180 DefaultRoots {
181 merkle_root,
182 block_commitments_hash,
183 chain_history_root,
184 ..
185 },
186 bits: difficulty_threshold,
187 ref coinbase_txn,
188 transactions: ref tx_templates,
189 ..
190 } = *template;
191
192 let height = Height(height);
193
194 if height > Height::MAX {
196 Err(SerializationError::Parse(
197 "height of coinbase transaction is {height}, which exceeds the maximum of {Height::MAX}",
198 ))?;
199 };
200
201 let time = time_source
202 .into()
203 .unwrap_or_default()
204 .time_from_template(template)
205 .into();
206
207 let mut transactions = vec![coinbase_txn.data.as_ref().zcash_deserialize_into()?];
208
209 for tx_template in tx_templates {
210 transactions.push(tx_template.data.as_ref().zcash_deserialize_into()?);
211 }
212
213 let commitment_bytes = match NetworkUpgrade::current(net, height) {
214 NetworkUpgrade::Canopy => chain_history_root.bytes_in_serialized_order(),
215 NetworkUpgrade::Nu5 | NetworkUpgrade::Nu6 | NetworkUpgrade::Nu6_1 | NetworkUpgrade::Nu7 => {
216 block_commitments_hash.bytes_in_serialized_order()
217 }
218 _ => Err(SerializationError::Parse(
219 "Zebra does not support generating pre-Canopy block templates",
220 ))?,
221 }
222 .into();
223
224 Ok(Block {
225 header: Arc::new(block::Header {
226 version,
227 previous_block_hash,
228 merkle_root,
229 commitment_bytes,
230 time,
231 difficulty_threshold,
232 nonce: [0; 32].into(),
233 solution: Solution::for_proposal(),
234 }),
235 transactions,
236 })
237}