Skip to content

Commit acb935e

Browse files
committed
feat: add dropped blocks to tenure forking info API
1 parent da31b7d commit acb935e

File tree

3 files changed

+129
-14
lines changed

3 files changed

+129
-14
lines changed

stackslib/src/chainstate/nakamoto/staging_blocks.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,30 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> {
428428
.collect())
429429
}
430430

431+
/// Get all nakamoto blocks in a tenure
432+
pub fn get_nakamoto_blocks_in_tenure(
433+
&self,
434+
consensus_hash: &ConsensusHash,
435+
) -> Result<Vec<NakamotoBlock>, ChainstateError> {
436+
let qry =
437+
"SELECT data FROM nakamoto_staging_blocks WHERE consensus_hash = ?1 AND processed = 1";
438+
let args = params![consensus_hash];
439+
let block_data: Vec<Vec<u8>> = query_rows(self, qry, args)?;
440+
Ok(block_data
441+
.into_iter()
442+
.filter_map(|block_vec| {
443+
NakamotoBlock::consensus_deserialize(&mut &block_vec[..])
444+
.map_err(|e| {
445+
error!("Failed to deserialize block from DB, likely database corruption";
446+
"consensus_hash" => %consensus_hash,
447+
"error" => ?e);
448+
e
449+
})
450+
.ok()
451+
})
452+
.collect())
453+
}
454+
431455
/// Find the next ready-to-process Nakamoto block, given a connection to the staging blocks DB.
432456
/// NOTE: the relevant field queried from `nakamoto_staging_blocks` are updated by a separate
433457
/// tx from block-processing, so it's imperative that the thread that calls this function is

stackslib/src/net/api/get_tenures_fork_info.rs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState, NakamotoSta
3434
use crate::chainstate::stacks::db::StacksChainState;
3535
use crate::chainstate::stacks::Error as ChainError;
3636
use crate::net::api::getblock_v3::NakamotoBlockStream;
37-
use crate::net::api::{prefix_hex, prefix_opt_hex};
37+
use crate::net::api::{prefix_hex, prefix_opt_hex, prefix_opt_hex_codec};
3838
use crate::net::http::{
3939
parse_bytes, parse_json, Error, HttpBadRequest, HttpChunkGenerator, HttpContentType,
4040
HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse,
@@ -80,6 +80,9 @@ pub struct TenureForkingInfo {
8080
/// tenure's first block.
8181
#[serde(with = "prefix_opt_hex")]
8282
pub first_block_mined: Option<StacksBlockId>,
83+
/// Nakamoto blocks in the tenure
84+
#[serde(with = "prefix_opt_hex_codec")]
85+
pub nakamoto_blocks: Option<Vec<NakamotoBlock>>,
8386
}
8487

8588
#[derive(Clone, Default)]
@@ -154,8 +157,8 @@ impl TenureForkingInfo {
154157
chainstate: &StacksChainState,
155158
tip_block_id: &StacksBlockId,
156159
) -> Result<Self, ChainError> {
157-
let first_block_mined = if !sn.sortition {
158-
None
160+
let (first_block_mined, nakamoto_blocks) = if !sn.sortition {
161+
(None, None)
159162
} else {
160163
// is this a nakamoto sortition?
161164
let epoch = SortitionDB::get_stacks_epoch(sortdb.conn(), sn.block_height)?.ok_or_else(
@@ -168,18 +171,28 @@ impl TenureForkingInfo {
168171
},
169172
)?;
170173
if epoch.epoch_id < StacksEpochId::Epoch30 {
171-
StacksChainState::get_stacks_block_header_info_by_consensus_hash(
172-
chainstate.db(),
173-
&sn.consensus_hash,
174-
)?
175-
.map(|header| header.index_block_hash())
174+
(
175+
StacksChainState::get_stacks_block_header_info_by_consensus_hash(
176+
chainstate.db(),
177+
&sn.consensus_hash,
178+
)?
179+
.map(|header| header.index_block_hash()),
180+
None,
181+
)
176182
} else {
177-
NakamotoChainState::get_nakamoto_tenure_start_block_header(
178-
&mut chainstate.index_conn(),
179-
tip_block_id,
180-
&sn.consensus_hash,
181-
)?
182-
.map(|header| header.index_block_hash())
183+
(
184+
NakamotoChainState::get_nakamoto_tenure_start_block_header(
185+
&mut chainstate.index_conn(),
186+
tip_block_id,
187+
&sn.consensus_hash,
188+
)?
189+
.map(|header| header.index_block_hash()),
190+
Some(
191+
chainstate
192+
.nakamoto_blocks_db()
193+
.get_nakamoto_blocks_in_tenure(&sn.consensus_hash)?,
194+
),
195+
)
183196
}
184197
};
185198
Ok(TenureForkingInfo {
@@ -190,6 +203,7 @@ impl TenureForkingInfo {
190203
consensus_hash: sn.consensus_hash.clone(),
191204
was_sortition: sn.sortition,
192205
first_block_mined,
206+
nakamoto_blocks,
193207
})
194208
}
195209
}

stackslib/src/net/api/mod.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum};
2424
use stacks_common::util::HexError;
2525

2626
use crate::burnchains::Txid;
27+
use crate::chainstate::nakamoto::NakamotoBlock;
2728
use crate::chainstate::stacks::{StacksMicroblock, StacksTransaction};
2829
use crate::core::mempool;
2930
use crate::cost_estimates::FeeRateEstimate;
@@ -225,6 +226,82 @@ pub mod prefix_hex {
225226
}
226227
}
227228

229+
/// This module serde encode and decodes structs that
230+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
231+
pub mod prefix_hex_codec {
232+
use clarity::codec::StacksMessageCodec;
233+
use clarity::util::hash::{hex_bytes, to_hex};
234+
235+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
236+
val: &T,
237+
s: S,
238+
) -> Result<S::Ok, S::Error> {
239+
let mut bytes = vec![];
240+
val.consensus_serialize(&mut bytes)
241+
.map_err(serde::ser::Error::custom)?;
242+
s.serialize_str(&format!("0x{}", to_hex(&bytes)))
243+
}
244+
245+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
246+
d: D,
247+
) -> Result<T, D::Error> {
248+
let inst_str: String = serde::Deserialize::deserialize(d)?;
249+
let Some(hex_str) = inst_str.get(2..) else {
250+
return Err(serde::de::Error::invalid_length(
251+
inst_str.len(),
252+
&"at least length 2 string",
253+
));
254+
};
255+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
256+
T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)
257+
}
258+
}
259+
260+
/// This module serde encode and decodes structs that
261+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
262+
/// This is the same as prefix_hex_codec, but for Option<T>.
263+
pub mod prefix_opt_hex_codec {
264+
use clarity::codec::StacksMessageCodec;
265+
use clarity::util::hash::{hex_bytes, to_hex};
266+
267+
use super::prefix_hex_codec;
268+
269+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
270+
val: &Option<T>,
271+
s: S,
272+
) -> Result<S::Ok, S::Error> {
273+
match val {
274+
Some(ref some_val) => {
275+
let mut bytes = vec![];
276+
some_val
277+
.consensus_serialize(&mut bytes)
278+
.map_err(serde::ser::Error::custom)?;
279+
let hex_string = format!("0x{}", to_hex(&bytes));
280+
s.serialize_some(&hex_string)
281+
}
282+
None => s.serialize_none(),
283+
}
284+
}
285+
286+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
287+
d: D,
288+
) -> Result<Option<T>, D::Error> {
289+
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
290+
let Some(inst_string) = opt_inst_str else {
291+
return Ok(None);
292+
};
293+
let Some(hex_str) = inst_string.get(2..) else {
294+
return Err(serde::de::Error::invalid_length(
295+
inst_string.len(),
296+
&"at least length 2 string",
297+
));
298+
};
299+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
300+
let val = T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)?;
301+
Ok(Some(val))
302+
}
303+
}
304+
228305
pub trait HexDeser: Sized {
229306
fn try_from(hex: &str) -> Result<Self, HexError>;
230307
}

0 commit comments

Comments
 (0)